#!/usr/bin/env perl

use strict;
use warnings;
use utf8;

use Config ();
use Capture::Tiny qw(capture);
use Cwd qw(cwd);
use FindBin qw($Bin);
use lib grep { defined && -d } ( $ENV{DEVELOPER_DASHBOARD_REPO_LIB}, "$Bin/../lib" );
use File::Spec;
use Getopt::Long qw(GetOptionsFromArray);
use Getopt::Long ();

binmode STDOUT, ':encoding(UTF-8)';
binmode STDERR, ':encoding(UTF-8)';

use Developer::Dashboard::Platform qw(
  native_shell_name
  normalize_shell_name
  shell_quote_for
);
use Developer::Dashboard::CLI::SeededPages ();
use Developer::Dashboard::InternalCLI ();
use Developer::Dashboard::SeedSync ();

my $dashboard_lib = File::Spec->catdir( $Bin, '..', 'lib' );
if ( -d $dashboard_lib ) {
    my $path_sep = $Config::Config{path_sep} || ':';
    my @existing = grep { defined && $_ ne '' } split /\Q$path_sep\E/, ( $ENV{PERL5LIB} || '' );
    if ( !grep { $_ eq $dashboard_lib } @existing ) {
        $ENV{PERL5LIB} = join $path_sep, $dashboard_lib, @existing;
    }
}

my $cmd = shift @ARGV || die "Missing built-in dashboard command\n";
$0 = join q{ }, 'developer-dashboard', $cmd, @ARGV;

require Developer::Dashboard::Auth;
require Developer::Dashboard::ActionRunner;
require Developer::Dashboard::Codec;
Developer::Dashboard::Codec->import(qw(encode_payload decode_payload));
require Developer::Dashboard::CollectorRunner;
require Developer::Dashboard::Collector;
require Developer::Dashboard::Config;
require Developer::Dashboard;
require Developer::Dashboard::DockerCompose;
require Developer::Dashboard::Doctor;
require Developer::Dashboard::FileRegistry;
require Developer::Dashboard::Housekeeper;
require Developer::Dashboard::IndicatorStore;
require Developer::Dashboard::JSON;
Developer::Dashboard::JSON->import(qw(json_encode));
require Developer::Dashboard::PageDocument;
require Developer::Dashboard::PageRuntime;
require Developer::Dashboard::PageResolver;
require Developer::Dashboard::PageStore;
require Developer::Dashboard::PathRegistry;
require Developer::Dashboard::Prompt;
require Developer::Dashboard::CLI::Progress;
require Developer::Dashboard::RuntimeManager;
require Developer::Dashboard::SessionStore;
require Developer::Dashboard::Web::App;
require Developer::Dashboard::Web::Server;

my $paths = Developer::Dashboard::PathRegistry->new(
    workspace_roots => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
    project_roots   => [ grep { defined && -d } map { "$ENV{HOME}/$_" } qw(projects src work) ],
);
_apply_runtime_perl5lib( paths => $paths );
my $files      = Developer::Dashboard::FileRegistry->new( paths => $paths );
my $indicators = Developer::Dashboard::IndicatorStore->new( paths => $paths );
my $collectors = Developer::Dashboard::Collector->new( paths => $paths );
my $runner     = Developer::Dashboard::CollectorRunner->new(
    collectors => $collectors,
    files      => $files,
    indicators => $indicators,
    paths      => $paths,
);
my $config     = Developer::Dashboard::Config->new( files => $files, paths => $paths );
$files->register_named_files( $config->file_aliases );
my $pages      = Developer::Dashboard::PageStore->new( paths => $paths );
my $page_runtime = Developer::Dashboard::PageRuntime->new(
    files   => $files,
    paths   => $paths,
    aliases => $config->path_aliases,
);
my $auth = Developer::Dashboard::Auth->new(
    files => $files,
    paths => $paths,
);
my $actions = Developer::Dashboard::ActionRunner->new(
    files => $files,
    paths => $paths,
);
my $resolver = Developer::Dashboard::PageResolver->new(
    actions => $actions,
    config  => $config,
    pages   => $pages,
    paths   => $paths,
);
my $docker = Developer::Dashboard::DockerCompose->new(
    config  => $config,
    paths   => $paths,
);
my $doctor = Developer::Dashboard::Doctor->new(
    paths => $paths,
);
my $housekeeper = Developer::Dashboard::Housekeeper->new(
    paths => $paths,
);
my $prompt = Developer::Dashboard::Prompt->new(
    paths      => $paths,
    indicators => $indicators,
);
my $sessions = Developer::Dashboard::SessionStore->new( paths => $paths );
my $runtime = Developer::Dashboard::RuntimeManager->new(
    app_builder => sub {
        my (%args) = @_;
        my $web_settings = $config->web_settings;
        my $web_auth = Developer::Dashboard::Auth->new( files => $files, paths => $paths );
        my $web_pages = Developer::Dashboard::PageStore->new( paths => $paths );
        my $web_sessions = Developer::Dashboard::SessionStore->new( paths => $paths );
        my $app = Developer::Dashboard::Web::App->new(
            actions  => $actions,
            auth     => $web_auth,
            config   => $config,
            pages    => $web_pages,
            prompt   => $prompt,
            runtime  => $page_runtime,
            resolver => $resolver,
            sessions => $web_sessions,
        );
        return Developer::Dashboard::Web::Server->new(
            app     => $app,
            host    => $args{host},
            port    => $args{port},
            workers => $args{workers},
            ssl     => $args{ssl},
            ssl_subject_alt_names => $web_settings->{ssl_subject_alt_names},
        );
    },
    config => $config,
    files  => $files,
    paths  => $paths,
    runner => $runner,
);

my $CONFIG_PATH_ALIASES_LOADED  = 0;
my $SAVED_PAGES_MIGRATED        = 0;
my $COLLECTOR_INDICATORS_SYNCED = 0;

# _prime_ticket_ref_env()
# Seeds TICKET_REF from the active tmux session when the shell environment does
# not already provide it.
# Input: none.
# Output: true after best-effort environment priming.
sub _prime_ticket_ref_env {
    return 1 if defined $ENV{TICKET_REF} && $ENV{TICKET_REF} ne '';
    my $ticket = _tmux_ticket_ref();
    $ENV{TICKET_REF} = $ticket if defined $ticket && $ticket ne '';
    return 1;
}

# _tmux_ticket_ref()
# Reads TICKET_REF from tmux session environment when available.
# Input: none.
# Output: ticket string or undef when tmux does not expose one.
sub _tmux_ticket_ref {
    my ( $stdout, undef, $exit_code ) = capture {
        system 'tmux', 'show-environment', 'TICKET_REF';
        return $? >> 8;
    };
    return if $exit_code != 0;
    return if !defined $stdout || $stdout eq '';
    for my $line ( split /\n/, $stdout ) {
        next if !defined $line || $line eq '' || $line =~ /^-/;
        return $1 if $line =~ /^TICKET_REF=(.+)$/;
    }
    return;
}

sub _load_configured_path_aliases {
    return 1 if $CONFIG_PATH_ALIASES_LOADED;
    $paths->register_named_paths( $config->path_aliases );
    $CONFIG_PATH_ALIASES_LOADED = 1;
    return 1;
}

sub _migrate_saved_pages {
    return 1 if $SAVED_PAGES_MIGRATED;
    $pages->migrate_legacy_json_pages;
    $SAVED_PAGES_MIGRATED = 1;
    return 1;
}

sub _collector_jobs {
    return $config->collectors;
}

# _find_collector_job($name)
# Returns the configured collector job definition for one collector name.
# Input: collector name string.
# Output: collector job hash reference or undef when not configured.
sub _find_collector_job {
    my ($name) = @_;
    return if !defined $name || $name eq '';
    my ($job) = grep { $_->{name} eq $name } @{ _collector_jobs() };
    return $job;
}

# _known_collector_names()
# Returns the union of configured and persisted collector names in stable order.
# Input: none.
# Output: ordered list of collector name strings.
sub _known_collector_names {
    my %seen;
    my @names;
    for my $job ( @{ _collector_jobs() } ) {
        my $name = $job->{name};
        next if !defined $name || $name eq '' || $seen{$name}++;
        push @names, $name;
    }
    for my $status ( $collectors->list_collectors ) {
        my $name = $status->{name};
        next if !defined $name || $name eq '' || $seen{$name}++;
        push @names, $name;
    }
    return @names;
}

# _collector_known($name)
# Returns whether a collector is known through config or persisted runtime state.
# Input: collector name string.
# Output: boolean true when the collector name is known.
sub _collector_known {
    my ($name) = @_;
    return 0 if !defined $name || $name eq '';
    return 1 if _find_collector_job($name);
    return $collectors->collector_exists($name) ? 1 : 0;
}

sub _sync_configured_collector_indicators {
    return 1 if $COLLECTOR_INDICATORS_SYNCED;
    $indicators->sync_collectors( _collector_jobs() );
    $COLLECTOR_INDICATORS_SYNCED = 1;
    return 1;
}

# _runtime_cpanfile_path(%args)
# Resolves the project-local cpanfile used by dashboard cpan installs.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute cpanfile path string.
sub _runtime_cpanfile_path {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    return File::Spec->catfile( $active_paths->runtime_root, 'cpanfile' );
}

# _runtime_local_root(%args)
# Resolves and creates the project-local cpanm installation root.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute local-root path string.
sub _runtime_local_root {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $dir = File::Spec->catdir( $active_paths->runtime_root, 'local' );
    if ( !-d $dir ) {
        require File::Path;
        File::Path::make_path($dir);
    }
    if ( $active_paths->can('secure_dir_permissions') ) {
        $active_paths->secure_dir_permissions($dir);
    }
    return $dir;
}

# _runtime_local_lib(%args)
# Resolves the runtime-local Perl library directory exposed to dashboard subprocesses.
# Input: hash containing an optional path registry under the "paths" key.
# Output: absolute local Perl library path string.
sub _runtime_local_lib {
    my (%args) = @_;
    return File::Spec->catdir( _runtime_local_root(%args), 'lib', 'perl5' );
}

# _runtime_perl5lib_env(%args)
# Builds the PERL5LIB environment update for runtime-local optional modules.
# Input: hash containing an optional path registry under the "paths" key.
# Output: hash containing the merged PERL5LIB value.
sub _runtime_perl5lib_env {
    my (%args) = @_;
    my $path_sep = $Config::Config{path_sep} || ':';
    my @perl5lib = grep { defined $_ && $_ ne '' } split /\Q$path_sep\E/, ( $ENV{PERL5LIB} || '' );
    my @local_libs;
    if ( my $paths = $args{paths} ) {
        if ( $paths->can('runtime_local_lib_roots') ) {
            @local_libs = $paths->runtime_local_lib_roots;
        }
        else {
            @local_libs = ( _runtime_local_lib(%args) );
        }
    }
    else {
        @local_libs = ( _runtime_local_lib(%args) );
    }
    for my $local_lib ( reverse @local_libs ) {
        next if !defined $local_lib || $local_lib eq '';
        next if !-d $local_lib;
        next if grep { $_ eq $local_lib } @perl5lib;
        unshift @perl5lib, $local_lib;
    }
    return (
        PERL5LIB => join( $path_sep, @perl5lib ),
    );
}

# _apply_runtime_perl5lib(%args)
# Exposes the runtime-local Perl library to the current dashboard process.
# Input: hash containing an optional path registry under the "paths" key.
# Output: true value.
sub _apply_runtime_perl5lib {
    my (%args) = @_;
    my %env = _runtime_perl5lib_env(%args);
    @ENV{ keys %env } = values %env;
    return 1;
}

# _normalize_runtime_cpan_modules(@modules)
# Validates requested module names, trims whitespace, and removes duplicates while preserving order.
# Input: zero or more requested module-name strings.
# Output: ordered list of validated module-name strings.
sub _normalize_runtime_cpan_modules {
    my (@modules) = @_;
    my @normalized;
    my %seen;
    for my $module (@modules) {
        next if !defined $module;
        $module =~ s/^\s+//;
        $module =~ s/\s+$//;
        next if $module eq '';
        die "Invalid module name '$module'\n" if $module !~ /\A[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z0-9_]+)*\z/;
        next if $seen{$module}++;
        push @normalized, $module;
    }
    return @normalized;
}

# _effective_runtime_cpan_modules(@modules)
# Expands requested module names so any requested DBD driver also installs DBI.
# Input: ordered list of validated requested module-name strings.
# Output: ordered list of module names that should be installed and persisted.
sub _effective_runtime_cpan_modules {
    my (@modules) = @_;
    my @effective = @modules;
    if ( grep { /^DBD::/ } @modules ) {
        unshift @effective, 'DBI' if !grep { $_ eq 'DBI' } @effective;
    }
    my %seen;
    return grep { !$seen{$_}++ } @effective;
}

# _append_runtime_cpan_modules(%args)
# Appends missing module requirements to the runtime cpanfile without duplicating existing entries.
# Input: hash containing an optional path registry under the "paths" key plus a required array reference under "modules".
# Output: absolute cpanfile path string.
sub _append_runtime_cpan_modules {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $modules      = $args{modules};
    die "No modules provided\n" if ref($modules) ne 'ARRAY' || !@{$modules};
    my $cpanfile = _runtime_cpanfile_path( paths => $active_paths );
    my %existing;
    if ( -f $cpanfile ) {
        open my $in, '<', $cpanfile or die "Unable to read $cpanfile: $!";
        while ( my $line = <$in> ) {
            next if $line !~ /requires\s+'([^']+)'/;
            $existing{$1} = 1;
        }
        close $in or die "Unable to close $cpanfile: $!";
    }
    open my $out, '>>', $cpanfile or die "Unable to append $cpanfile: $!";
    for my $module ( @{$modules} ) {
        next if $existing{$module};
        print {$out} "requires '$module';\n";
    }
    close $out or die "Unable to close $cpanfile: $!";
    if ( $active_paths->can('secure_file_permissions') ) {
        $active_paths->secure_file_permissions($cpanfile);
    }
    return $cpanfile;
}

# _install_runtime_cpan_modules(%args)
# Installs optional runtime Perl modules into the project-local local-lib tree and records them in the runtime cpanfile.
# Input: hash containing an optional path registry under the "paths" key and a required array reference of requested modules under "modules".
# Output: result hash reference containing ok/error state, effective module list, paths, and captured command output.
sub _install_runtime_cpan_modules {
    my (%args) = @_;
    my $active_paths = $args{paths} || $paths;
    my $modules      = $args{modules};
    return { error => 'No modules provided' } if ref($modules) ne 'ARRAY' || !@{$modules};

    my @requested = _normalize_runtime_cpan_modules( @{$modules} );
    return { error => 'No valid modules provided' } if !@requested;

    my @install_modules = _effective_runtime_cpan_modules(@requested);
    my $local_root      = _runtime_local_root( paths => $active_paths );

    my ( $stdout, $stderr, $exit ) = capture {
        system( 'cpanm', '-L', $local_root, @install_modules );
        return $? >> 8;
    };
    return {
        error        => "Failed to install runtime Perl modules with cpanm: $stderr",
        stdout       => $stdout,
        stderr       => $stderr,
        requested    => \@requested,
        installed    => \@install_modules,
        runtime_root => $active_paths->runtime_root,
        local_root   => $local_root,
        cpanfile     => _runtime_cpanfile_path( paths => $active_paths ),
    } if $exit != 0;

    _append_runtime_cpan_modules(
        paths   => $active_paths,
        modules => \@install_modules,
    );
    _apply_runtime_perl5lib( paths => $active_paths );

    return {
        ok           => 1,
        stdout       => $stdout,
        stderr       => $stderr,
        requested    => \@requested,
        installed    => \@install_modules,
        runtime_root => $active_paths->runtime_root,
        local_root   => $local_root,
        cpanfile     => _runtime_cpanfile_path( paths => $active_paths ),
    };
}

if ( $cmd eq 'encode' ) {
    local $/;
    my $text = <STDIN>;
    print encode_payload($text), "\n";
    exit 0;
}
elsif ( $cmd eq 'decode' ) {
    local $/;
    my $token = <STDIN>;
    $token =~ s/\s+$//;
    print decode_payload($token);
    exit 0;
}
elsif ( $cmd eq 'indicator' ) {
    _sync_configured_collector_indicators() if ( ( $ARGV[0] || '' ) eq 'list' );
    my $action = shift @ARGV || '';
    if ( $action eq 'set' ) {
        my ( $name, $label, $icon, $status ) = @ARGV;
        die "Usage: dashboard indicator set <name> <label> <icon> <status>\n" if !$status;
        my $item = $indicators->set_indicator(
            $name,
            label          => $label,
            icon           => $icon,
            status         => $status,
            priority       => 100,
            prompt_visible => 1,
        );
        print json_encode($item);
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $indicators->list_indicators ] );
        exit 0;
    }
    elsif ( $action eq 'refresh-core' ) {
        my $cwd = shift @ARGV || cwd();
        print json_encode( $indicators->refresh_core_indicators( cwd => $cwd ) );
        exit 0;
    }
}
elsif ( $cmd eq 'collector' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'write-result' ) {
        my ( $name, $exit_code ) = @ARGV;
        die "Usage: dashboard collector write-result <name> <exit_code>\n" if !defined $exit_code;
        local $/;
        my $stdout = <STDIN>;
        $collectors->write_result(
            $name,
            exit_code => $exit_code,
            stdout    => defined $stdout ? $stdout : '',
            stderr    => '',
        );
        exit 0;
    }
    elsif ( $action eq 'status' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector status <name>\n";
        print json_encode( $collectors->read_status($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $collectors->list_collectors ] );
        exit 0;
    }
    elsif ( $action eq 'job' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector job <name>\n";
        print json_encode( $collectors->read_job($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'output' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector output <name>\n";
        print json_encode( $collectors->read_output($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'inspect' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector inspect <name>\n";
        print json_encode( $collectors->inspect_collector($name) || {} );
        exit 0;
    }
    elsif ( $action eq 'log' ) {
        my $name = shift @ARGV;
        die "Usage: dashboard collector log [name]\n" if @ARGV;
        if ( defined $name && $name ne '' ) {
            die "Unknown collector '$name'\n" if !_collector_known($name);
            my $log = $collectors->read_log($name);
            if ( !defined $log || $log eq '' ) {
                print "No log entries are available yet for collector '$name'.\n";
                exit 0;
            }
            print $log;
            exit 0;
        }
        my @names = _known_collector_names();
        if ( !@names ) {
            print "No collector logs are available yet.\n";
            exit 0;
        }
        my @logs;
        for my $collector_name (@names) {
            my $log = $collectors->read_log($collector_name);
            $log = "No log entries are available yet for collector '$collector_name'.\n"
              if !defined $log || $log eq '';
            push @logs, $log;
        }
        print join "\n", @logs;
        exit 0;
    }
    elsif ( $action eq 'run' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector run <name>\n";
        my $job = _find_collector_job($name);
        die "Unknown collector '$name'\n" if !$job;
        print json_encode( $runner->run_once($job) );
        exit 0;
    }
    elsif ( $action eq 'start' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector start <name>\n";
        my $started = $runtime->start_named_collector( name => $name );
        print $started->{pid} . "\n";
        exit 0;
    }
    elsif ( $action eq 'stop' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector stop <name>\n";
        my $pid = $runner->stop_loop($name);
        print defined $pid ? "$pid\n" : '';
        exit 0;
    }
    elsif ( $action eq 'restart' ) {
        my $name = shift @ARGV || die "Usage: dashboard collector restart <name>\n";
        my $result = $runtime->restart_target( scope => 'collector', name => $name );
        my $collector = $result->{collectors}[0] || {};
        print $collector->{pid} . "\n" if defined $collector->{pid};
        exit 0;
    }
}
elsif ( $cmd eq 'config' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'init' ) {
        my $file = $config->ensure_global_file;
        print "$file\n";
        exit 0;
    }
    elsif ( $action eq 'show' ) {
        _load_configured_path_aliases();
        print json_encode( $config->merged );
        exit 0;
    }
}
elsif ( $cmd eq 'housekeeper' ) {
    print json_encode( $housekeeper->run );
    exit 0;
}
elsif ( $cmd eq 'auth' ) {
    my $action = shift @ARGV || '';
    if ( $action eq 'add-user' ) {
        my ( $username, $password ) = @ARGV;
        die "Usage: dashboard auth add-user <username> <password>\n" if !$username || !$password;
        print json_encode(
            $auth->add_user(
                username => $username,
                password => $password,
            )
        );
        exit 0;
    }
    elsif ( $action eq 'list-users' ) {
        print json_encode( [ $auth->list_users ] );
        exit 0;
    }
    elsif ( $action eq 'remove-user' ) {
        my $username = shift @ARGV || die "Usage: dashboard auth remove-user <username>\n";
        $auth->remove_user($username);
        print json_encode( { removed => $username } );
        exit 0;
    }
}
elsif ( $cmd eq 'init' ) {
    _migrate_saved_pages();
    my $migrated = [];
    my $config_file = $config->ensure_global_file;

    my @pages = $pages->list_saved_pages;
    my $internal_cli = Developer::Dashboard::InternalCLI::ensure_helpers( paths => $paths );

    print json_encode(
        {
            config_file => $config_file,
            internal_cli => $internal_cli,
            runtime_root => $paths->runtime_root,
            migrated_pages => $migrated,
            pages        => [ $pages->list_saved_pages ],
        }
    );
    exit 0;
}
elsif ( $cmd eq 'cpan' ) {
    my @modules = @ARGV;
    my $result = eval {
        _install_runtime_cpan_modules(
            paths   => $paths,
            modules => \@modules,
        );
    };
    if ($@) {
        my $error = $@;
        $error =~ s/\s+\z//;
        die "$error\n";
    }
    die $result->{error} . "\n" if $result->{error};
    print json_encode($result);
    exit 0;
}
elsif ( $cmd eq 'page' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $action = shift @ARGV || '';
    if ( $action eq 'new' ) {
        my $id = shift @ARGV || '';
        my $title = shift @ARGV || 'Untitled';
        my $page = Developer::Dashboard::PageDocument->new(
            id          => $id || undef,
            title       => $title,
            description => 'Project-neutral Developer Dashboard page',
            layout      => {
                body => "Replace this body with your own page content.",
            },
            state => {
                project => '',
            },
            actions => [
                { id => 'example', label => 'Example Action' },
            ],
        );
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'save' ) {
        my $id = shift @ARGV || die "Usage: dashboard page save <id>\n";
        local $/;
        my $source = <STDIN>;
        my $page = $source =~ /^\s*\{/
          ? Developer::Dashboard::PageDocument->from_json($source)
          : Developer::Dashboard::PageDocument->from_instruction($source);
        $page->{id} = $id;
        my $file = $pages->save_page($page);
        print "$file\n";
        exit 0;
    }
    elsif ( $action eq 'list' ) {
        print json_encode( [ $resolver->list_pages ] );
        exit 0;
    }
    elsif ( $action eq 'show' ) {
        my $id = shift @ARGV || die "Usage: dashboard page show <id>\n";
        my $page = $resolver->load_named_page($id);
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'encode' ) {
        my $id = shift @ARGV;
        my $page;
        if ($id) {
            $page = $pages->load_saved_page($id);
        }
        else {
            local $/;
            my $source = <STDIN>;
            $page = $source =~ /^\s*\{/
              ? Developer::Dashboard::PageDocument->from_json($source)
              : Developer::Dashboard::PageDocument->from_instruction($source);
        }
        print $pages->encode_page($page), "\n";
        exit 0;
    }
    elsif ( $action eq 'decode' ) {
        my $token = shift @ARGV || do {
            local $/;
            scalar <STDIN>;
        };
        $token =~ s/\s+$// if defined $token;
        my $page = $pages->load_transient_page($token);
        print $page->canonical_instruction;
        exit 0;
    }
    elsif ( $action eq 'urls' ) {
        my $id = shift @ARGV || die "Usage: dashboard page urls <id>\n";
        my $page = $pages->load_saved_page($id);
        print json_encode(
            {
                edit   => $pages->editable_url($page),
                render => $pages->render_url($page),
                source => $pages->source_url($page),
            }
        );
        exit 0;
    }
    elsif ( $action eq 'render' ) {
        my $source = shift @ARGV || '';
        my $page;
        my $source_kind = 'transient';
        if ($source) {
            if ( -f $source ) {
                open my $fh, '<', $source or die "Unable to read $source: $!";
                local $/;
                my $raw = <$fh>;
                $page = $raw =~ /^\s*\{/
                  ? Developer::Dashboard::PageDocument->from_json($raw)
                  : Developer::Dashboard::PageDocument->from_instruction($raw);
            }
            else {
                $page = $resolver->load_named_page($source);
                $source_kind = $page->{meta}{source_kind} || 'saved';
            }
        }
        else {
            local $/;
            my $source = <STDIN>;
            $page = $source =~ /^\s*\{/
              ? Developer::Dashboard::PageDocument->from_json($source)
              : Developer::Dashboard::PageDocument->from_instruction($source);
        }
        $page = $page_runtime->prepare_page(
            page            => $page,
            source          => $source_kind,
            runtime_context => { params => {} },
        );
        $page->with_mode('render');
        print $page->render_html;
        exit 0;
    }
    elsif ( $action eq 'source' ) {
        my $source = shift @ARGV || die "Usage: dashboard page source <id|token>\n";
        my $page = eval { $resolver->load_named_page($source) };
        if ( !$page ) {
            die $@ if $source !~ /^[A-Za-z0-9+\/=]+$/;
            $page = $pages->load_transient_page($source);
        }
        $page->with_mode('source');
        print $page->canonical_instruction;
        exit 0;
    }
}
elsif ( $cmd eq 'action' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $sub = shift @ARGV || '';
    if ( $sub eq 'run' ) {
        my $page_id = shift @ARGV || die "Usage: dashboard action run <page_id> <action_id>\n";
        my $action_id = shift @ARGV || die "Usage: dashboard action run <page_id> <action_id>\n";
        my $page = $resolver->load_named_page($page_id);
        my ($action) = grep { ref($_) eq 'HASH' && ( $_->{id} || '' ) eq $action_id } @{ $page->as_hash->{actions} || [] };
        die "Unknown action '$action_id'\n" if !$action;
        print json_encode(
            $actions->run_page_action(
                action => $action,
                page   => $page,
                source => $page->{meta}{source_kind} || 'saved',
            )
        );
        exit 0;
    }
}
elsif ( $cmd eq 'docker' ) {
    _load_configured_path_aliases();
    my $sub = shift @ARGV || '';
    if ( $sub eq 'compose' ) {
        my @addons;
        my @modes;
        my @services;
        my $project_root = '';
        my $dry_run = 0;
        Getopt::Long::Configure(qw(pass_through no_getopt_compat no_auto_abbrev));
        GetOptionsFromArray(
            \@ARGV,
            'addon=s@'   => \@addons,
            'mode=s@'    => \@modes,
            'service=s@' => \@services,
            'project=s'  => \$project_root,
            'dry-run!'   => \$dry_run,
        );
        Getopt::Long::Configure(qw(no_pass_through getopt_compat auto_abbrev));
        my $result = $docker->resolve(
            addons       => \@addons,
            args         => \@ARGV,
            modes        => \@modes,
            services     => \@services,
            project_root => $project_root || undef,
        );
        if ($dry_run) {
            print json_encode($result);
            exit 0;
        }
        chdir $result->{project_root} or die "Unable to chdir to $result->{project_root}: $!";
        local @ENV{ keys %{ $result->{env} } } = values %{ $result->{env} } if %{ $result->{env} };
        exec @{ $result->{command} };
        die "Unable to exec docker compose: $!";
    }
    if ( $sub eq 'disable' ) {
        my $service = shift @ARGV || die "Usage: dashboard docker disable <service>\n";
        die "Usage: dashboard docker disable <service>\n" if @ARGV;
        print json_encode(
            $docker->disable_service(
                service => $service,
            )
        );
        exit 0;
    }
    if ( $sub eq 'enable' ) {
        my $service = shift @ARGV || die "Usage: dashboard docker enable <service>\n";
        die "Usage: dashboard docker enable <service>\n" if @ARGV;
        print json_encode(
            $docker->enable_service(
                service => $service,
            )
        );
        exit 0;
    }
    if ( $sub eq 'list' ) {
        my $enabled_only  = 0;
        my $disabled_only = 0;
        GetOptionsFromArray(
            \@ARGV,
            'enabled!'  => \$enabled_only,
            'disabled!' => \$disabled_only,
        );
        die "Usage: dashboard docker list [--enabled|--disabled]\n"
          if @ARGV || ( $enabled_only && $disabled_only );
        my $filter = 'all';
        $filter = 'enabled'  if $enabled_only;
        $filter = 'disabled' if $disabled_only;
        print json_encode(
            $docker->list_services(
                filter => $filter,
            )
        );
        exit 0;
    }
}
elsif ( $cmd eq 'serve' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $action = @ARGV && $ARGV[0] !~ /^-/ ? shift @ARGV : '';
    if ( $action eq 'logs' ) {
        my $follow = 0;
        my $lines;
        GetOptionsFromArray(
            \@ARGV,
            'f'   => \$follow,
            'n=i' => \$lines,
        );
        print $runtime->web_log(
            follow => $follow,
            ( defined $lines ? ( lines => $lines ) : () ),
        );
        exit 0;
    }
    if ( $action eq 'workers' ) {
        my $workers = shift @ARGV;
        my $host = '0.0.0.0';
        my $port = 7890;
        GetOptionsFromArray(
            \@ARGV,
            'host=s' => \$host,
            'port=i' => \$port,
        );
        my $saved = $config->save_global_web_workers($workers);
        my $running = $runtime->running_web;
        my $pid;
        my $collectors = [];
        if ( !$running ) {
            my $served = $runtime->serve_all(
                host    => $host,
                port    => $port,
                workers => $saved->{workers},
            );
            $pid = $served->{pid};
            $collectors = $served->{collectors} || [];
        }
        print json_encode(
            {
                %{$saved},
                ( defined $pid ? ( pid => $pid ) : () ),
                ( @{$collectors} ? ( collectors => $collectors ) : () ),
            }
        );
        exit 0;
    }
    unshift @ARGV, $action if defined $action && $action ne '';
    my $settings = $config->web_settings;
    my $host = $settings->{host};
    my $port = $settings->{port};
    my $workers = $settings->{workers};
    my $ssl = $settings->{ssl};
    my $editor_enabled = $settings->{no_editor} ? 0 : 1;
    my $indicators_enabled = $settings->{no_indicators} ? 0 : 1;
    my $foreground = 0;
    GetOptionsFromArray(
        \@ARGV,
        'host=s'        => \$host,
        'port=i'        => \$port,
        'workers=i'     => \$workers,
        'ssl!'          => \$ssl,
        'editor!'       => \$editor_enabled,
        'endit!'        => \$editor_enabled,
        'indicator!'    => \$indicators_enabled,
        'indicators!'   => \$indicators_enabled,
        'foreground!'   => \$foreground,
    );
    $config->save_global_web_settings(
        host          => $host,
        port          => $port,
        workers       => $workers,
        ssl           => $ssl,
        no_editor     => $editor_enabled ? 0 : 1,
        no_indicators => $indicators_enabled ? 0 : 1,
    );
    my $result = $runtime->serve_all(
        foreground => $foreground,
        host       => $host,
        port       => $port,
        workers    => $workers,
        ssl        => $ssl,
    );
    if (!$foreground) {
        print json_encode($result);
    }
    exit 0;
}
elsif ( $cmd eq 'web-foreground' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    my $settings = $config->web_settings;
    my $host = $settings->{host};
    my $port = $settings->{port};
    my $workers = $settings->{workers};
    my $ssl = $settings->{ssl};
    GetOptionsFromArray(
        \@ARGV,
        'host=s'    => \$host,
        'port=i'    => \$port,
        'workers=i' => \$workers,
        'ssl!'      => \$ssl,
    );
    die "Usage: dashboard web-foreground [--host HOST] [--port PORT] [--workers N] [--ssl|--no-ssl]\n" if @ARGV;
    $runtime->start_web(
        foreground => 1,
        host       => $host,
        port       => $port,
        workers    => $workers,
        ssl        => $ssl,
    );
    exit 0;
}
elsif ( $cmd eq 'collector-supervisor-foreground' ) {
    _load_configured_path_aliases();
    _migrate_saved_pages();
    $runtime->_run_collector_supervisor_child(
        daemonize => 0,
        redirect  => 0,
    );
    exit 0;
}
elsif ( $cmd eq 'stop' ) {
    require Developer::Dashboard::CLI::RuntimeControl;
    exit Developer::Dashboard::CLI::RuntimeControl::run_runtime_command(
        command    => $cmd,
        args       => \@ARGV,
        runtime    => $runtime,
        config     => $config,
        collectors => $collectors,
    );
}
elsif ( $cmd eq 'restart' ) {
    _load_configured_path_aliases();
    require Developer::Dashboard::CLI::RuntimeControl;
    exit Developer::Dashboard::CLI::RuntimeControl::run_runtime_command(
        command    => $cmd,
        args       => \@ARGV,
        runtime    => $runtime,
        config     => $config,
        collectors => $collectors,
    );
}
elsif ( $cmd eq 'log' || $cmd eq 'logs' ) {
    require Developer::Dashboard::CLI::RuntimeControl;
    exit Developer::Dashboard::CLI::RuntimeControl::run_runtime_command(
        command    => $cmd,
        args       => \@ARGV,
        runtime    => $runtime,
        config     => $config,
        collectors => $collectors,
    );
}
elsif ( $cmd eq 'shell' ) {
    my $shell = normalize_shell_name( shift @ARGV || native_shell_name() );
    print _shell_bootstrap($shell);
    exit 0;
}
elsif ( $cmd eq 'doctor' ) {
    my $fix = 0;
    GetOptionsFromArray(
        \@ARGV,
        'fix!' => \$fix,
    );
    print json_encode(
        $doctor->run(
            fix => $fix,
        )
    );
    exit 0;
}
elsif ( $cmd eq 'skills' ) {
    require Developer::Dashboard::CLI::Skills;
    my $exit_code = Developer::Dashboard::CLI::Skills::run_skills_command(
        command => $cmd,
        args    => \@ARGV,
    );
    exit $exit_code;
}
elsif ( $cmd eq 'complete' ) {
    require Developer::Dashboard::CLI::Complete;
    my $index = shift @ARGV;
    die "Usage: dashboard complete <index> <word0> <word1> ...\n" if !defined $index || $index !~ /\A\d+\z/;
    my @candidates = Developer::Dashboard::CLI::Complete::complete(
        index => $index,
        words => \@ARGV,
    );
    print join( "\n", @candidates ), "\n" if @candidates;
    exit 0;
}
else {
    die "Unsupported built-in dashboard command '$cmd'\n";
}

# _lifecycle_progress(%args)
# Builds an optional terminal progress board for restart/stop lifecycle commands.
# Input: title string and ordered task array reference.
# Output: Developer::Dashboard::CLI::Progress object or undef when disabled.
sub _lifecycle_progress {
    my (%args) = @_;
    my $enabled = $ENV{DEVELOPER_DASHBOARD_PROGRESS} ? 1 : 0;
    $enabled = 1 if !$enabled && -t STDERR;
    return if !$enabled;
    return Developer::Dashboard::CLI::Progress->new(
        title   => $args{title},
        tasks   => $args{tasks},
        stream  => \*STDERR,
        dynamic => ( -t STDERR ? 1 : 0 ),
        color   => ( -t STDERR ? 1 : 0 ),
    );
}

# _seed_init_page($pages, $known_ids, $page)
# Saves or refreshes one seeded page when the existing target is still the last
# known dashboard-managed copy, while preserving diverged user-edited pages.
# Input: page store, array reference of known ids, and page document object.
# Output: true when the page already existed or was written.
sub _seed_init_page {
    my ( $pages, $known_ids, $page ) = @_;
    my $id = $page->as_hash->{id} || return 1;
    Developer::Dashboard::CLI::SeededPages::ensure_seeded_page(
        page  => $page,
        pages => $pages,
        paths => $paths,
    );
    push @{ $known_ids || [] }, $id;
    return 1;
}

# _shell_dashboard_command()
# Builds a shell-safe command that re-invokes the current dashboard entrypoint.
# Input: none.
# Output: shell command string suitable for generated shell bootstrap helpers.
sub _shell_dashboard_command {
    my ($shell) = @_;
    my $script = $ENV{DEVELOPER_DASHBOARD_ENTRYPOINT} || File::Spec->rel2abs($0);
    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        my $repo_lib = $ENV{DEVELOPER_DASHBOARD_REPO_LIB} || File::Spec->rel2abs( File::Spec->catdir( $Bin, '..', 'lib' ) );
        if ( -f File::Spec->catfile( $repo_lib, 'Developer', 'Dashboard.pm' ) ) {
            return join ' ',
              shell_quote_for( $shell, $^X ),
              shell_quote_for( $shell, '-I' . $repo_lib ),
              shell_quote_for( $shell, $script );
        }
        return join ' ',
          shell_quote_for( $shell, $^X ),
          shell_quote_for( $shell, $script );
    }
    return shell_quote_for( $shell, $script );
}

# _shell_bootstrap($shell)
# Builds the requested shell bootstrap script with shared navigation helpers and
# shell-specific prompt wiring.
# Input: normalized shell name.
# Output: shell bootstrap script string.
sub _shell_bootstrap {
    my ($shell) = @_;
    my $dashboard_cmd = _shell_dashboard_command($shell);
    my $tmux_status_cmd = shell_quote_for( 'sh', $ENV{DEVELOPER_DASHBOARD_ENTRYPOINT} || File::Spec->rel2abs($0) );
    my $dashboard_json_matches = _shell_json_extract_command(
        $shell,
        'my $h=JSON::XS->new->decode($_); print join qq(\n), @{ $h->{matches} || [] }',
    );
    my $dashboard_json_target = _shell_json_extract_command(
        $shell,
        'my $h=JSON::XS->new->decode($_); print $h->{target} // q{}',
    );
    my $bootstrap = _shell_navigation_bootstrap($shell) . "\n" . _shell_prompt_bootstrap($shell);
    $bootstrap =~ s/__DASHBOARD_CMD__/$dashboard_cmd/g;
    $bootstrap =~ s/__DASHBOARD_TMUX_STATUS_CMD__/$tmux_status_cmd/g;
    $bootstrap =~ s/__DASHBOARD_JSON_MATCHES__/$dashboard_json_matches/g;
    $bootstrap =~ s/__DASHBOARD_JSON_TARGET__/$dashboard_json_target/g;
    return $bootstrap;
}

# _shell_navigation_bootstrap()
# Builds the portable shell helpers used by all supported interactive shells for
# bookmark-aware navigation.
# Input: none.
# Output: shell function definitions as a script string.
sub _shell_navigation_bootstrap {
    my ($shell) = @_;
    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        return <<'POWERSHELL';
function Get-DashboardTarget {
  param([string[]]$DashboardArgs)
  if (-not $DashboardArgs -or $DashboardArgs.Count -eq 0) {
    return [pscustomobject]@{ target = $null; matches = @() }
  }
  $json = & __DASHBOARD_CMD__ path cdr @DashboardArgs
  if ($LASTEXITCODE -ne 0 -or -not $json) {
    return [pscustomobject]@{ target = $null; matches = @() }
  }
  $parsed = $json | ConvertFrom-Json
  $matches = @()
  if ($parsed.matches) {
    $matches = @($parsed.matches)
  }
  return [pscustomobject]@{
    target = if ($parsed.target) { [string]$parsed.target } else { $null }
    matches = $matches
  }
}

function cdr {
  param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DashboardArgs)
  $selection = Get-DashboardTarget -DashboardArgs $DashboardArgs
  if ($selection.matches.Count -gt 0) {
    $selection.matches
  }
  if ($selection.target) {
    Set-Location $selection.target
  }
}

function Invoke-DashboardShortcut {
  param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DashboardArgs)
  & __DASHBOARD_CMD__ @DashboardArgs
}

Set-Alias dd_cdr cdr
Set-Alias d2 Invoke-DashboardShortcut

Register-ArgumentCompleter -CommandName 'dashboard', 'd2' -ScriptBlock {
  param($wordToComplete, $commandAst, $cursorPosition)
  $tokens = @()
  foreach ($element in $commandAst.CommandElements) {
    if ($null -eq $element) {
      continue
    }
    $tokens += [string]$element.Extent.Text
  }
  if ($tokens.Count -eq 0) {
    return
  }
  $index = $tokens.Count - 1
  if ($commandAst.Extent.EndOffset -gt $cursorPosition) {
    $prefixCount = 0
    foreach ($element in $commandAst.CommandElements) {
      if ($null -eq $element) {
        continue
      }
      last if $element.Extent.EndOffset -ge $cursorPosition
      $prefixCount++
    }
    $index = $prefixCount
    $tokens = if ($prefixCount -gt 0) { @($tokens[0..($prefixCount - 1)]) } else { @() }
    $tokens += [string]$wordToComplete
  }
  $results = & __DASHBOARD_CMD__ complete $index @tokens
  foreach ($candidate in @($results)) {
    if (-not [string]::IsNullOrEmpty($candidate)) {
      [System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
    }
  }
}

Register-ArgumentCompleter -CommandName 'cdr', 'dd_cdr', 'which_dir' -ScriptBlock {
  param($wordToComplete, $commandAst, $cursorPosition)
  $tokens = @()
  foreach ($element in $commandAst.CommandElements) {
    if ($null -eq $element) {
      continue
    }
    $tokens += [string]$element.Extent.Text
  }
  if ($tokens.Count -eq 0) {
    return
  }
  $index = $tokens.Count - 1
  if ($commandAst.Extent.EndOffset -gt $cursorPosition) {
    $prefixCount = 0
    foreach ($element in $commandAst.CommandElements) {
      if ($null -eq $element) {
        continue
      }
      last if $element.Extent.EndOffset -ge $cursorPosition
      $prefixCount++
    }
    $index = $prefixCount
    $tokens = if ($prefixCount -gt 0) { @($tokens[0..($prefixCount - 1)]) } else { @() }
    $tokens += [string]$wordToComplete
  }
  $results = & __DASHBOARD_CMD__ path complete-cdr $index @tokens
  foreach ($candidate in @($results)) {
    if (-not [string]::IsNullOrEmpty($candidate)) {
      [System.Management.Automation.CompletionResult]::new($candidate, $candidate, 'ParameterValue', $candidate)
    }
  }
}

function which_dir {
  param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DashboardArgs)
  $selection = Get-DashboardTarget -DashboardArgs $DashboardArgs
  if ($selection.matches.Count -gt 0) {
    $selection.matches
    return
  }
  if ($selection.target) {
    $selection.target
  }
}
POWERSHELL
    }
    if ( $shell eq 'zsh' ) {
        return <<'ZSH';
cdr() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
  fi
  if [ -n "$target" ]; then
    cd "$target"
  fi
}

dd_cdr() {
  cdr "$@"
}

d2() {
  __DASHBOARD_CMD__ "$@"
}

which_dir() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
    return
  fi
  if [ -n "$target" ]; then
    printf '%s\n' "$target"
  fi
}

# Ensure compdef exists in fresh zsh shells before registering completions.
autoload -Uz compinit
if ! whence compdef >/dev/null 2>&1; then
  compinit
fi

_dashboard_complete_zsh() {
  local -a completions
  completions=("${(@f)$(__DASHBOARD_CMD__ complete "$((CURRENT-1))" "${words[@]}")}")
  compadd -a completions
}

compdef _dashboard_complete_zsh dashboard d2

_dashboard_complete_cdr_zsh() {
  local -a completions
  completions=("${(@f)$(__DASHBOARD_CMD__ path complete-cdr "$((CURRENT-1))" "${words[@]}")}")
  compadd -a completions
}

compdef _dashboard_complete_cdr_zsh cdr dd_cdr which_dir
ZSH
    }
    if ( $shell eq 'bash' ) {
        return <<'BASH';
cdr() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
  fi
  if [ -n "$target" ]; then
    cd "$target"
  fi
}

dd_cdr() {
  cdr "$@"
}

d2() {
  __DASHBOARD_CMD__ "$@"
}

which_dir() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
    return
  fi
  if [ -n "$target" ]; then
    printf '%s\n' "$target"
  fi
}

_dashboard_complete() {
  COMPREPLY=()
  local candidate
  local completion_output
  completion_output="$(__DASHBOARD_CMD__ complete "$COMP_CWORD" "${COMP_WORDS[@]}")"
  while IFS= read -r candidate; do
    [ -n "$candidate" ] || continue
    COMPREPLY+=("$candidate")
  done <<EOF
$completion_output
EOF
}

complete -F _dashboard_complete dashboard d2

_dashboard_complete_cdr() {
  COMPREPLY=()
  local candidate
  local completion_output
  completion_output="$(__DASHBOARD_CMD__ path complete-cdr "$COMP_CWORD" "${COMP_WORDS[@]}")"
  while IFS= read -r candidate; do
    [ -n "$candidate" ] || continue
    COMPREPLY+=("$candidate")
  done <<EOF
$completion_output
EOF
}

complete -F _dashboard_complete_cdr cdr dd_cdr which_dir
BASH
    }
    return <<'SH';
cdr() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
  fi
  if [ -n "$target" ]; then
    cd "$target"
  fi
}

dd_cdr() {
  cdr "$@"
}

d2() {
  __DASHBOARD_CMD__ "$@"
}

which_dir() {
  payload="$(__DASHBOARD_CMD__ path cdr "$@")"
  matches="$(printf '%s' "$payload" | __DASHBOARD_JSON_MATCHES__)"
  target="$(printf '%s' "$payload" | __DASHBOARD_JSON_TARGET__)"
  if [ -n "$matches" ]; then
    printf '%s\n' "$matches"
    return
  fi
  if [ -n "$target" ]; then
    printf '%s\n' "$target"
  fi
}
SH
}

# _shell_json_extract_command($shell, $perl_code)
# Builds the shell-safe JSON decoding command used by generated shell helpers.
# Input: normalized shell name plus one Perl one-liner body string.
# Output: shell command string suitable for use on the right-hand side of a pipe.
sub _shell_json_extract_command {
    my ( $shell, $perl_code ) = @_;
    my $repo_lib = $ENV{DEVELOPER_DASHBOARD_REPO_LIB} || File::Spec->rel2abs( File::Spec->catdir( $Bin, '..', 'lib' ) );
    my @command = ($^X);
    if ( -f File::Spec->catfile( $repo_lib, 'Developer', 'Dashboard.pm' ) ) {
        push @command, '-I' . $repo_lib;
    }
    push @command, '-MJSON::XS', '-0777', '-ne', $perl_code;

    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        return join ' ', '&', map { shell_quote_for( $shell, $_ ) } @command;
    }
    return join ' ', map { shell_quote_for( $shell, $_ ) } @command;
}

# _shell_prompt_bootstrap($shell)
# Builds the prompt hook snippet for a supported shell so dashboard ps1 can be
# reused outside bash as well.
# Input: normalized shell name.
# Output: shell snippet string.
sub _shell_prompt_bootstrap {
    my ($shell) = @_;
    if ( $shell eq 'bash' ) {
        return <<'BASH';

# Guard for macOS Terminal.app update_terminal_cwd
if [ -z "${update_terminal_cwd+x}" ] && [ -f /etc/bashrc_Apple_Terminal ]; then
  source /etc/bashrc_Apple_Terminal 2>/dev/null || true
fi

_dd_tmux_status_active() {
  [ -n "${TMUX:-}" ] && { [ "${DEVELOPER_DASHBOARD_TMUX_STATUS:-0}" = "1" ] || [ -n "${TICKET_REF:-}" ]; }
}
_dd_apply_tmux_ticket_status() {
  _dd_default_status="$(tmux show-options -gqv @dd_ticket_status_default 2>/dev/null)"
  if [ -z "$_dd_default_status" ]; then
    _dd_default_status="$(tmux show-options -gqv status-format[0] 2>/dev/null)"
    [ -n "$_dd_default_status" ] && tmux set-option -gq @dd_ticket_status_default "$_dd_default_status" >/dev/null 2>&1
  fi
  tmux set-option -q status-position bottom >/dev/null 2>&1
  tmux set-option -q status 2 >/dev/null 2>&1
  tmux set-option -q status-interval 2 >/dev/null 2>&1
  tmux set-option -q status-format[0] "#(__DASHBOARD_TMUX_STATUS_CMD__ ps1 --mode tmux-status-top --width #{client_width})" >/dev/null 2>&1
  if [ -n "$_dd_default_status" ]; then
    tmux set-option -q status-format[1] "$_dd_default_status" >/dev/null 2>&1
  fi
  tmux set-option -uq status-format[2] >/dev/null 2>&1
}
_dd_update_prompt() {
  if [ -n "$TMUX" ] && _dd_tmux_status_active; then
    _dd_apply_tmux_ticket_status
    PS1='$(__DASHBOARD_CMD__ ps1 --jobs \j --mode compact --no-indicators)'
  else
    PS1='$(__DASHBOARD_CMD__ ps1 --jobs \j --mode compact)'
  fi
  export PS1
}
case ";${PROMPT_COMMAND:-};" in
  *";_dd_update_prompt;"*) ;;
  *)
    if [ -n "${PROMPT_COMMAND:-}" ]; then
      PROMPT_COMMAND="_dd_update_prompt;${PROMPT_COMMAND}"
    else
      PROMPT_COMMAND="_dd_update_prompt"
    fi
    export PROMPT_COMMAND
    ;;
esac
_dd_update_prompt
BASH
    }
    if ( $shell eq 'zsh' ) {
        return <<'ZSH';

autoload -Uz add-zsh-hook
_dd_tmux_status_active() {
  [[ -n "${TMUX:-}" && ( "${DEVELOPER_DASHBOARD_TMUX_STATUS:-0}" = "1" || -n "${TICKET_REF:-}" ) ]]
}
_dd_apply_tmux_ticket_status() {
  local default_status
  default_status="$(tmux show-options -gqv @dd_ticket_status_default 2>/dev/null)"
  if [[ -z "$default_status" ]]; then
    default_status="$(tmux show-options -gqv status-format[0] 2>/dev/null)"
    [[ -n "$default_status" ]] && tmux set-option -gq @dd_ticket_status_default "$default_status" >/dev/null 2>&1
  fi
  tmux set-option -q status-position bottom >/dev/null 2>&1
  tmux set-option -q status 2 >/dev/null 2>&1
  tmux set-option -q status-interval 2 >/dev/null 2>&1
  tmux set-option -q status-format[0] "#(__DASHBOARD_TMUX_STATUS_CMD__ ps1 --mode tmux-status-top --width #{client_width})" >/dev/null 2>&1
  if [[ -n "$default_status" ]]; then
    tmux set-option -q status-format[1] "$default_status" >/dev/null 2>&1
  fi
  tmux set-option -uq status-format[2] >/dev/null 2>&1
}
_dd_update_prompt() {
  if _dd_tmux_status_active; then
    _dd_apply_tmux_ticket_status
    PS1="$(__DASHBOARD_CMD__ ps1 --jobs ${#jobstates} --mode compact --no-indicators)"
  else
    PS1="$(__DASHBOARD_CMD__ ps1 --jobs ${#jobstates} --mode compact)"
  fi
}
add-zsh-hook precmd _dd_update_prompt
_dd_update_prompt
ZSH
    }
    if ( $shell eq 'sh' ) {
        return <<'SH';

_dd_tmux_status_active() {
  [ -n "${TMUX:-}" ] && { [ "${DEVELOPER_DASHBOARD_TMUX_STATUS:-0}" = "1" ] || [ -n "${TICKET_REF:-}" ]; }
}
_dd_apply_tmux_ticket_status() {
  _dd_default_status="$(tmux show-options -gqv @dd_ticket_status_default 2>/dev/null)"
  if [ -z "$_dd_default_status" ]; then
    _dd_default_status="$(tmux show-options -gqv status-format[0] 2>/dev/null)"
    [ -n "$_dd_default_status" ] && tmux set-option -gq @dd_ticket_status_default "$_dd_default_status" >/dev/null 2>&1
  fi
  tmux set-option -q status-position bottom >/dev/null 2>&1
  tmux set-option -q status 2 >/dev/null 2>&1
  tmux set-option -q status-interval 2 >/dev/null 2>&1
  tmux set-option -q status-format[0] "#(__DASHBOARD_TMUX_STATUS_CMD__ ps1 --mode tmux-status-top --width #{client_width})" >/dev/null 2>&1
  if [ -n "$_dd_default_status" ]; then
    tmux set-option -q status-format[1] "$_dd_default_status" >/dev/null 2>&1
  fi
  tmux set-option -uq status-format[2] >/dev/null 2>&1
}
_dd_prompt_command() {
  if _dd_tmux_status_active; then
    __DASHBOARD_CMD__ ps1 --mode compact --no-indicators
  else
    __DASHBOARD_CMD__ ps1 --mode compact
  fi
}
if _dd_tmux_status_active; then
  _dd_apply_tmux_ticket_status
fi
PS1='$(_dd_prompt_command)'
export PS1
SH
    }
    if ( $shell eq 'powershell' || $shell eq 'pwsh' ) {
        return <<'POWERSHELL';
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
function Test-DashboardTmuxStatusActive {
  return [bool]($env:TMUX -and (($env:DEVELOPER_DASHBOARD_TMUX_STATUS -eq '1') -or -not [string]::IsNullOrWhiteSpace($env:TICKET_REF)))
}
function Set-DashboardTmuxTicketStatus {
  $ddDefaultStatus = (& tmux show-options -gqv @dd_ticket_status_default 2>$null) -join ''
  if ([string]::IsNullOrWhiteSpace($ddDefaultStatus)) {
    $ddDefaultStatus = (& tmux show-options -gqv status-format[0] 2>$null) -join ''
    if (-not [string]::IsNullOrWhiteSpace($ddDefaultStatus)) {
      & tmux set-option -gq @dd_ticket_status_default $ddDefaultStatus | Out-Null
    }
  }
  & tmux set-option -q status-position bottom | Out-Null
  & tmux set-option -q status 2 | Out-Null
  & tmux set-option -q status-interval 2 | Out-Null
  & tmux set-option -q status-format[0] "#(__DASHBOARD_TMUX_STATUS_CMD__ ps1 --mode tmux-status-top --width #{client_width})" | Out-Null
  if (-not [string]::IsNullOrWhiteSpace($ddDefaultStatus)) {
    & tmux set-option -q status-format[1] $ddDefaultStatus | Out-Null
  }
  & tmux set-option -uq status-format[2] | Out-Null
}
if (Test-DashboardTmuxStatusActive) {
  Set-DashboardTmuxTicketStatus
}
function prompt {
  if (Test-DashboardTmuxStatusActive) {
    $ddPrompt = & __DASHBOARD_CMD__ ps1 --mode compact --no-indicators
  } else {
    $ddPrompt = & __DASHBOARD_CMD__ ps1 --mode compact
  }
  $ddPromptText = ((@($ddPrompt | Where-Object { $null -ne $_ } | ForEach-Object { "$_" })) -join [Environment]::NewLine)
  if ([string]::IsNullOrWhiteSpace($ddPromptText)) {
    return '> '
  }
  $ddPromptText = $ddPromptText.TrimEnd("`r", "`n")
  $ddPromptLines = @($ddPromptText -split "`r?`n")
  if ($ddPromptLines.Length -gt 1) {
    $header = ($ddPromptLines[0..($ddPromptLines.Length - 2)] -join [Environment]::NewLine)
    if ($header -ne '') {
      Write-Host $header
    }
    return $ddPromptLines[$ddPromptLines.Length - 1]
  }
  return $ddPromptText
}
POWERSHELL
    }
    die "Unsupported shell '$shell'\n";
}

__END__

=head1 NAME

_dashboard-core - internal staged built-in command runtime for Developer Dashboard

=head1 SYNOPSIS

  _dashboard-core <built-in-command> [args...]

=head1 DESCRIPTION

This internal helper runs the heavier built-in Developer Dashboard commands
after the public C<dashboard> entrypoint has already handled hook execution and
switchboard dispatch. It is staged under F<~/.developer-dashboard/cli/dd/> as
F<_dashboard-core> and is not meant to be called by users directly. It also
generates the shared shell bootstrap snippets that expose C<cdr>,
C<dd_cdr>, C<d2>, C<which_dir>, prompt wiring, and tab-completion handlers
across bash, zsh, POSIX sh, and PowerShell. While it is running, it rewrites
its process title to the public-facing C<developer-dashboard ...> form so
process listings do not leak the staged helper path.

__END__

=pod

=head1 NAME

_dashboard-core - contains the shared private built-in command runtime that the staged helper scripts hand off into.

=for comment FULL-POD-DOC START

=head1 PURPOSE

This private staged runtime owns the heavier built-in command implementations after C<bin/dashboard> has already done hook execution and helper dispatch. It is also where the generated shell bootstrap snippets come from, including C<cdr>, C<which_dir>, and prompt wiring across bash, zsh, POSIX sh, and PowerShell. Detached internal foreground helpers such as the web listener and collector watchdog also re-enter through this file when the runtime needs a background-managed process without bloating the public entrypoint. While it runs, it normalizes the live process title to C<developer-dashboard ...> so operators see the public command name in C<ps> output instead of the staged helper path under F<~/.developer-dashboard/cli/dd/>.

=head1 WHY IT EXISTS

It exists so the public entrypoint stays thin while dashboard-managed built-ins still share one internal command runtime. The staged helper wrappers can all hand off into this file instead of each carrying their own copy of command logic.

=head1 WHEN TO USE

Use this file when changing built-in command dispatch after helper staging, shell bootstrap generation, detached foreground helper entrypoints such as the web listener or collector watchdog, or behavior shared across multiple staged helper wrappers.

=head1 HOW TO USE

Users do not call this file directly. C<dashboard init> or on-demand staging writes wrappers under F<~/.developer-dashboard/cli/dd/>, and those wrappers invoke C<_dashboard-core> with the built-in command name they represent.

=head1 WHAT USES IT

It is used by every staged dashboard-managed helper under F<~/.developer-dashboard/cli/dd/>, by shell bootstrap generation, and by tests that verify the private-helper runtime contract.

=head1 EXAMPLES

Example 1:

  dashboard paths

Run the public built-in command path that stages or re-enters this helper.

Example 2:

  ~/.developer-dashboard/cli/dd/_dashboard-core --help

Inspect the staged helper directly after C<dashboard init> or helper extraction has populated the home runtime.

Example 3:

  prove -lv t/05-cli-smoke.t t/30-dashboard-loader.t

Rerun the focused staged-helper and thin-loader tests after changing helper dispatch behavior.

Example 4:

  prove -lr t

Verify that the helper still behaves correctly inside the complete repository suite.


=for comment FULL-POD-DOC END

=cut
