use strict;
use warnings;
use utf8;

use Capture::Tiny qw(capture);
use File::Spec;
use Test::More;
use File::Temp qw(tempdir);

use lib 'lib';

use Developer::Dashboard::Collector;
use Developer::Dashboard::Config;
use Developer::Dashboard::FileRegistry;
use Developer::Dashboard::IndicatorStore;
use Developer::Dashboard::PathRegistry;
use Developer::Dashboard::Prompt;

local $ENV{HOME} = tempdir(CLEANUP => 1);
chdir $ENV{HOME} or die "Unable to chdir to $ENV{HOME}: $!";

my $paths = Developer::Dashboard::PathRegistry->new;
my $collector = Developer::Dashboard::Collector->new(paths => $paths);
my $indicators = Developer::Dashboard::IndicatorStore->new(paths => $paths);
my $prompt = Developer::Dashboard::Prompt->new(paths => $paths, indicators => $indicators);

$collector->write_result(
    'example.collector',
    exit_code => 0,
    stdout    => "ok\n",
    stderr    => '',
);

my $status = $collector->read_status('example.collector');
is($status->{last_exit_code}, 0, 'collector status persisted');

$indicators->set_indicator(
    'docker',
    alias          => '🐳',
    label          => 'Docker',
    icon           => '🐳',
    page_status_icon => '&#x2705;',
    status         => 'ok',
    priority       => 10,
    prompt_visible => 1,
);

my @items = $indicators->list_indicators;
is(scalar @items, 1, 'one indicator listed');
is($items[0]{name}, 'docker', 'indicator stored');

local $ENV{TMUX} = '';
my $rendered = $prompt->render(jobs => 2, cwd => '/tmp/project');
like($rendered, qr/^\(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\)/, 'prompt renders legacy timestamp prefix');
like($rendered, qr/✅🐳.*\[\/tmp\/project\]/, 'compact prompt includes status glyph plus indicator icon before the bracketed path');
like($rendered, qr/\(2 jobs\)/, 'job count included');
like($rendered, qr/\n> \z/, 'prompt leaves the typing cursor marker on the next line');
my $tmux_status = $prompt->render_tmux_status(width => 200);
like($tmux_status, qr/✅🐳/, 'tmux status rendering still includes prompt-visible indicators');
unlike($tmux_status, qr/\n/, 'tmux status rendering stays on one line when the indicator strip fits');
like($tmux_status, qr/🕒\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/, 'tmux status rendering keeps the trailing date-time segment on the visible indicator line');
unlike($tmux_status, qr/🎫:/, 'tmux status rendering leaves ticket context to the prompt or tmux session line instead of duplicating it in the indicator strip');
like($prompt->render_tmux_status(line => 'top', width => 200), qr/✅🐳.*🕒\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/, 'tmux top status line includes prompt-visible indicators and the trailing date-time when the strip fits');
is($prompt->render_tmux_status(line => 'bottom', width => 200), q{}, 'tmux overflow status line stays empty when the top line can hold the full strip');
my $wrapped_tmux_status = $prompt->render_tmux_status(width => 4);
like($wrapped_tmux_status, qr/\n/, 'tmux status rendering emits an overflow line when the indicator strip does not fit on one line');
like($prompt->render_tmux_status(line => 'top', width => 4), qr/✅🐳\z/, 'tmux top status line keeps the first indicator when the strip wraps');
like($prompt->render_tmux_status(line => 'bottom', width => 4), qr/🕒\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/, 'tmux overflow status line carries the trailing date-time segment when the strip wraps');
{
    local $ENV{TMUX} = '';
    local $ENV{DEVELOPER_DASHBOARD_TMUX_STATUS} = 1;
    my $non_tmux_flag_prompt = $prompt->render(jobs => 0, cwd => '/tmp/project');
    like($non_tmux_flag_prompt, qr/✅🐳/, 'ticket-session tmux status flag alone does not suppress indicators outside tmux');
}
{
    local $ENV{TMUX} = 'tmux-session';
    local $ENV{DEVELOPER_DASHBOARD_TMUX_STATUS} = 1;
    my $tmux_prompt = $prompt->render(jobs => 0, cwd => '/tmp/project');
    unlike($tmux_prompt, qr/✅🐳/, 'inline prompt suppresses indicators automatically when tmux owns the status line');
    like($tmux_prompt, qr/\[\/tmp\/project\]/, 'inline prompt still includes the bracketed path when tmux owns the status line');
}
{
    local $ENV{TMUX} = 'tmux-session';
    local $ENV{DEVELOPER_DASHBOARD_TMUX_STATUS} = '';
    local $ENV{TICKET_REF} = '';
    local $ENV{WORKSPACE_REF} = '';
    my $plain_tmux_prompt = $prompt->render(jobs => 0, cwd => '/tmp/project');
    like($plain_tmux_prompt, qr/✅🐳/, 'plain tmux sessions keep inline indicators when dashboard-managed ticket markers are absent');
}
{
    local $ENV{TMUX} = 'tmux-session';
    local $ENV{DEVELOPER_DASHBOARD_TMUX_STATUS} = '';
    local $ENV{TICKET_REF} = 'DD-4242';
    local $ENV{WORKSPACE_REF} = '';
    my $ticket_tmux_prompt = $prompt->render(jobs => 0, cwd => '/tmp/project');
    unlike($ticket_tmux_prompt, qr/✅🐳/, 'ticket tmux prompts suppress inline indicators even when older sessions only expose TICKET_REF');
    like($ticket_tmux_prompt, qr/🎫:DD-4242/, 'ticket tmux prompts still render the ticket reference inline');
}
{
    my $empty_home = tempdir(CLEANUP => 1);
    my $empty_paths = Developer::Dashboard::PathRegistry->new(
        home            => $empty_home,
        cwd             => $empty_home,
        workspace_roots => [],
        project_roots   => [],
    );
    my $empty_indicators = Developer::Dashboard::IndicatorStore->new(paths => $empty_paths);
    my $empty_prompt = Developer::Dashboard::Prompt->new(paths => $empty_paths, indicators => $empty_indicators);
    like(
        $empty_prompt->render_tmux_status(width => 200),
        qr/\A🕒\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/,
        'tmux status rendering falls back to the timestamp-only line when no indicators are available',
    );
    is(
        $empty_prompt->render_tmux_status(line => 'bottom', width => 200),
        q{},
        'tmux overflow line stays empty when only the timestamp remains',
    );
}

$indicators->set_indicator(
    'stale',
    label          => 'Stale',
    icon           => 'S',
    status         => 'ok',
    priority       => 5,
    prompt_visible => 1,
);
$indicators->mark_stale('stale');
my $extended = $prompt->render(jobs => 0, cwd => '/tmp/project', mode => 'extended', max_age => 10);
like($extended, qr/SStale/, 'extended prompt still renders stale indicators');
unlike($extended, qr/SStale!/, 'extended prompt does not append invented stale punctuation');
like($extended, qr/✅🐳Docker/, 'extended prompt includes status glyph plus icon and label');
my $colored = $prompt->render(jobs => 0, cwd => '/tmp/project', color => 1);
like($colored, qr/\e\[/, 'prompt can render ANSI color escapes');
my $page_payload = $indicators->page_header_payload;
is_deeply(
    $page_payload->{array},
    [
        { prog => 'stale',  alias => 'S',  status => '&#x2705;' },
        { prog => 'docker', alias => '🐳', status => '&#x2705;' },
    ],
    'page header payload keeps indicator priority order while rendering status-plus-alias entries',
);
{
    my $config_home = tempdir(CLEANUP => 1);
    my $config_paths = Developer::Dashboard::PathRegistry->new(
        home            => $config_home,
        cwd             => $config_home,
        workspace_roots => [],
        project_roots   => [],
    );
    my $config_files = Developer::Dashboard::FileRegistry->new( paths => $config_paths );
    my $config = Developer::Dashboard::Config->new( files => $config_files, paths => $config_paths );
    my $collector_indicators = Developer::Dashboard::IndicatorStore->new(paths => $config_paths);
    $config->save_global(
        {
            collectors => [
                {
                    name      => 'enabled.collector',
                    command   => 'true',
                    interval  => 30,
                    disable   => 0,
                    indicator => { label => 'Enabled Collector' },
                },
                {
                    name      => 'disabled.collector',
                    command   => 'true',
                    interval  => 30,
                    disable   => 1,
                    indicator => { label => 'Disabled Collector' },
                },
            ],
        }
    );
    $collector_indicators->set_indicator(
        'disabled.collector',
        label                => 'Disabled Collector',
        configured_label     => 'Disabled Collector',
        collector_name       => 'disabled.collector',
        managed_by_collector => 1,
        status               => 'running',
        prompt_visible       => 1,
    );
    ok( $collector_indicators->collectors_need_sync( $config->collectors ), 'collector indicator sync notices disabled collectors as removals' );
    $collector_indicators->sync_collectors( $config->collectors );
    ok( !$collector_indicators->get_indicator('disabled.collector'), 'collector indicator sync removes disabled collector indicators' );
    ok( $collector_indicators->get_indicator('enabled.collector'), 'collector indicator sync still seeds enabled collector indicators' );
}

{
    my $repo = "$ENV{HOME}/repo";
    mkdir $repo or die $!;
    mkdir "$repo/.git" or die $!;
    no warnings 'redefine';
    local *Developer::Dashboard::Prompt::_git_branch = sub { 'main' };
    my $with_branch = $prompt->render( jobs => 0, cwd => $repo );
    like( $with_branch, qr/\[~\/repo\].*🌿main/, 'prompt includes git branch in the legacy trailing branch format' );
    like( $with_branch, qr/\n> \z/, 'prompt keeps the trailing command marker on its own next line when the branch suffix is present' );
}

my $core = $indicators->refresh_core_indicators( cwd => "$ENV{HOME}/repo" );
ok(ref($core) eq 'ARRAY' && @$core >= 2, 'core indicators can be refreshed on demand');
{
    my ( $stdout, $stderr, $exit_code ) = capture {
        $indicators->refresh_core_indicators( cwd => "$ENV{HOME}/repo" );
        return 0;
    };
    is( $stderr, '', 'core indicator refresh stays quiet for directories that only contain a placeholder .git path' );
}
{
    my $real_repo = File::Spec->catdir( $ENV{HOME}, 'real-git-repo' );
    mkdir $real_repo or die $!;
    system( 'git', 'init', '-q', $real_repo ) == 0 or die 'git init failed';
    system( 'git', '-C', $real_repo, 'config', 'user.email', 'indicator@example.test' ) == 0 or die 'git config user.email failed';
    system( 'git', '-C', $real_repo, 'config', 'user.name', 'Indicator Coverage' ) == 0 or die 'git config user.name failed';

    my $tracked = File::Spec->catfile( $real_repo, 'README' );
    open my $tracked_fh, '>', $tracked or die $!;
    print {$tracked_fh} "indicator coverage\n";
    close $tracked_fh;
    system( 'git', '-C', $real_repo, 'add', 'README' ) == 0 or die 'git add failed';
    system( 'git', '-C', $real_repo, 'commit', '-q', '-m', 'init' ) == 0 or die 'git commit failed';

    my $clean = $indicators->refresh_core_indicators( cwd => $real_repo );
    my ($clean_git) = grep { $_->{name} eq 'git' } @{$clean};
    is( $clean_git->{status}, 'clean', 'core indicator refresh marks a real clean git work tree as clean' );

    open my $dirty_fh, '>>', $tracked or die $!;
    print {$dirty_fh} "dirty\n";
    close $dirty_fh;

    my $dirty = $indicators->refresh_core_indicators( cwd => $real_repo );
    my ($dirty_git) = grep { $_->{name} eq 'git' } @{$dirty};
    is( $dirty_git->{status}, 'dirty', 'core indicator refresh marks a modified git work tree as dirty' );
}

done_testing;

__END__

=head1 NAME

02-indicator-collector.t - indicator and prompt integration tests

=head1 DESCRIPTION

This test verifies collector output persistence, indicator state storage, and
prompt rendering behavior.

=for comment FULL-POD-DOC START

=head1 PURPOSE

This test is the executable regression contract for collector execution, indicator state, and prompt integration. Read it when you need to understand the real fixture setup, assertions, and failure modes for this slice of the repository instead of guessing from the module names alone.

=head1 WHY IT EXISTS

It exists because collector execution, indicator state, and prompt integration has enough moving parts that a code-only review can miss real regressions. Keeping those expectations in a dedicated test file makes the TDD loop, coverage loop, and release gate concrete.

=head1 WHEN TO USE

Use this file when changing collector execution, indicator state, and prompt integration, when a focused CI failure points here, or when you want a faster regression loop than running the entire suite.

=head1 HOW TO USE

Run it directly with C<prove -lv t/02-indicator-collector.t> while iterating, then keep it green under C<prove -lr t> and the coverage runs before release. 

=head1 WHAT USES IT

Developers during TDD, the full C<prove -lr t> suite, the coverage gates, and the release verification loop all rely on this file to keep this behavior from drifting.

=head1 EXAMPLES

Example 1:

  prove -lv t/02-indicator-collector.t

Run the focused regression test by itself while you are changing the behavior it owns.

Example 2:

  HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lv t/02-indicator-collector.t

Exercise the same focused test while collecting coverage for the library code it reaches.

Example 3:

  prove -lr t

Put the focused fix back through the whole repository suite before calling the work finished.

=for comment FULL-POD-DOC END

=cut
