#!/usr/bin/env php
<?php

/**
 * Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
 *
 * This file is part of a Moko Consulting project.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * FILE INFORMATION
 * DEFGROUP: MokoStandards.CLI
 * INGROUP: MokoStandards
 * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
 * PATH: /bin/moko
 * BRIEF: Unified CLI dispatcher — run any MokoStandards script without needing GitHub Actions
 *
 * USAGE
 *   php bin/moko <command> [options]     (all platforms)
 *   ./bin/moko <command> [options]       (Unix, after: chmod +x bin/moko)
 *
 * COMMANDS (run `php bin/moko list` for the full list — 97 commands)
 *
 *   Automation            sync, automation:cleanup, automation:migrate-gitea
 *   Validation            health, detect, drift, check:syntax, check:version, ...
 *   Release               release, release:joomla, release:create, release:publish, ...
 *   Version               version:read, version:bump, version:auto-bump, ...
 *   Build                 build:package, build:joomla, build:updates-xml, ...
 *   Deploy                deploy:joomla, deploy:dolibarr, deploy:sftp, deploy:rollback, ...
 *   Repository            repo:create, repo:archive, repo:rename-branch, repo:reset-dev, ...
 *   Bulk Operations       bulk:push-workflow, bulk:push-manifest, bulk:template-joomla, ...
 *   Maintenance           maintenance:labels, maintenance:rotate-secrets, maintenance:pin-shas, ...
 *   Fix                   fix:line-endings, fix:tabs, fix:trailing, fix:permissions
 *   Monitoring            dashboard, grafana, client:inventory, client:health-check
 *   Platform              platform:detect, manifest:read, manifest:element
 *   Wiki                  wiki:sync
 *   Badges                badge:update
 *
 * COMMON OPTIONS (passed through to each script)
 *   --path <dir>          Repository root to check (default: .)
 *   --dry-run             Preview changes without applying them
 *   --verbose             Show passing checks as well as failures
 *   --quiet               Show only failures
 *   --json                Machine-readable JSON output
 *   --help                Show help for the selected command
 *
 * AUTHENTICATION
 *   Token resolution order (first non-empty wins):
 *     1. GH_TOKEN environment variable
 *     2. GITHUB_TOKEN environment variable
 *     3. `gh auth token` (GitHub CLI — run `gh auth login` once)
 *     4. .env file in repo root (GH_TOKEN=... line)
 *
 * EXAMPLES
 *   php bin/moko health
 *   php bin/moko sync -- --repos MokoDoliTraining --dry-run
 *   php bin/moko check:version --path .
 *   php bin/moko drift -- --org mokoconsulting-tech --json
 */

declare(strict_types=1);

// ── Bootstrap ────────────────────────────────────────────────────────────────

$repoRoot   = dirname(__DIR__);
$autoloader = $repoRoot . '/vendor/autoload.php';

// Support global Composer installs (e.g. composer global require)
if (isset($GLOBALS['_composer_autoload_path'])) {
    $autoloader = $GLOBALS['_composer_autoload_path'];
}

if (!is_file($autoloader)) {
    fwrite(STDERR, "Error: vendor/autoload.php not found.\nRun: composer install\n");
    exit(2);
}

require_once $autoloader;

// ── Command map ──────────────────────────────────────────────────────────────

/**
 * Map of moko command names → relative path to the PHP script.
 * All paths are relative to the repo root.
 */
const COMMAND_MAP = [
    // Audit
    'audit:query'                 => 'cli/audit_query.php',

    // Automation
    'sync'                        => 'automation/bulk_sync.php',
    'automation:cleanup'          => 'automation/repo_cleanup.php',
    'automation:migrate-gitea'    => 'automation/migrate_to_gitea.php',

    // Maintenance
    'inventory'                   => 'maintenance/update_repo_inventory.php',
    'maintenance:pin-shas'        => 'maintenance/pin_action_shas.php',
    'maintenance:inventory'       => 'maintenance/repo_inventory.php',
    'maintenance:rotate-secrets'  => 'maintenance/rotate_secrets.php',
    'maintenance:labels'          => 'maintenance/setup_labels.php',
    'maintenance:sync-dolibarr'   => 'maintenance/sync_dolibarr_readmes.php',
    'maintenance:update-shas'     => 'maintenance/update_sha_hashes.php',

    // Validation — general
    'health'           => 'validate/check_repo_health.php',
    'check:syntax'     => 'validate/check_php_syntax.php',
    'check:version'    => 'validate/check_version_consistency.php',
    'check:changelog'  => 'validate/check_changelog.php',
    'check:structure'  => 'validate/check_structure.php',
    'check:headers'    => 'validate/check_license_headers.php',
    'check:secrets'    => 'validate/check_no_secrets.php',
    'check:tabs'       => 'validate/check_tabs.php',
    'check:paths'      => 'validate/check_paths.php',
    'check:xml'        => 'validate/check_xml_wellformed.php',
    'check:enterprise' => 'validate/check_enterprise_readiness.php',

    // Validation — platform-specific
    'check:dolibarr'       => 'validate/check_dolibarr_module.php',
    'check:joomla'         => 'validate/check_joomla_manifest.php',
    'check:joomla-compat'  => 'cli/joomla_compat_check.php',
    'check:language'       => 'validate/check_language_structure.php',
    'check:client'         => 'validate/check_client_theme.php',
    'check:theme'          => 'cli/theme_lint.php',
    'check:wiki'           => 'validate/check_wiki_health.php',

    // Detection
    'detect'           => 'validate/auto_detect_platform.php',

    // Org-wide
    'drift'            => 'validate/scan_drift.php',

    // Release
    'release'              => 'cli/release.php',
    'release:notes'        => 'cli/release_notes.php',
    'release:validate'     => 'cli/release_validate.php',
    'release:cascade'      => 'cli/release_cascade.php',
    'release:promote'      => 'cli/release_promote.php',
    'release:create'       => 'cli/release_create.php',
    'release:manage'       => 'cli/release_manage.php',
    'release:mirror'       => 'cli/release_mirror.php',
    'release:package'      => 'cli/release_package.php',
    'release:joomla'       => 'cli/joomla_release.php',
    'release:body-update'  => 'cli/release_body_update.php',
    'release:publish'      => 'cli/release_publish.php',
    'release:verify'       => 'cli/release_verify.php',
    'release:gen-dolibarr' => 'release/generate_dolibarr_version_txt.php',
    'release:gen-joomla'   => 'release/generate_joomla_update_xml.php',

    // Changelog
    'changelog:promote'    => 'cli/changelog_promote.php',
    'changelog:prune'      => 'cli/changelog_prune.php',

    // Version management
    'version:read'         => 'cli/version_read.php',
    'version:bump'         => 'cli/version_bump.php',
    'version:check'        => 'cli/version_check.php',
    'version:propagate'    => 'maintenance/update_version_from_readme.php',
    'version:set-platform' => 'cli/version_set_platform.php',
    'version:reset-dev'    => 'cli/version_reset_dev.php',
    'version:auto-bump'    => 'cli/version_auto_bump.php',
    'version:bump-remote'  => 'cli/version_bump_remote.php',

    // Build & package
    'build:package'          => 'cli/package_build.php',
    'build:joomla'           => 'cli/joomla_build.php',
    'build:updates-xml'      => 'cli/updates_xml_build.php',
    'build:updates-xml-sync' => 'cli/updates_xml_sync.php',

    // Platform detection & manifest
    'platform:detect'      => 'cli/platform_detect.php',
    'manifest:read'        => 'cli/manifest_read.php',
    'manifest:element'     => 'cli/manifest_element.php',

    // Repository management
    'repo:create'          => 'cli/create_repo.php',
    'repo:create-project'  => 'cli/create_project.php',
    'repo:archive'         => 'cli/archive_repo.php',
    'repo:scaffold-client' => 'cli/scaffold_client.php',
    'repo:provision'       => 'cli/client_provision.php',
    'repo:rename-branch'   => 'cli/branch_rename.php',
    'repo:reset-dev'       => 'cli/dev_branch_reset.php',

    // Bulk operations
    'bulk:push-workflow'       => 'cli/bulk_workflow_push.php',
    'bulk:trigger'             => 'cli/bulk_workflow_trigger.php',
    'bulk:sync-rulesets'       => 'cli/sync_rulesets.php',
    'bulk:push-files'          => 'automation/push_files.php',
    'bulk:push-manifest'       => 'automation/push_manifest_xml.php',
    'bulk:push-mokostandards'  => 'automation/push_mokostandards_xml.php',
    'bulk:enrich-manifest'     => 'automation/enrich_manifest_xml.php',
    'bulk:enrich-mokostandards' => 'automation/enrich_mokostandards_xml.php',
    'bulk:template-joomla'     => 'automation/bulk_joomla_template.php',

    // Deploy
    'deploy:joomla'        => 'cli/deploy_joomla.php',
    'deploy:joomla-legacy' => 'deploy/deploy-joomla.php',
    'deploy:dolibarr'      => 'deploy/deploy-dolibarr.php',
    'deploy:sftp'          => 'deploy/deploy-sftp.php',
    'deploy:backup'        => 'deploy/backup-before-deploy.php',
    'deploy:health-check'  => 'deploy/health-check.php',
    'deploy:rollback'      => 'deploy/rollback-joomla.php',
    'deploy:sync'          => 'deploy/sync-joomla.php',

    // Fix / auto-remediation
    'fix:line-endings'     => 'fix/fix_line_endings.php',
    'fix:tabs'             => 'fix/fix_tabs.php',
    'fix:trailing'         => 'fix/fix_trailing_spaces.php',
    'fix:permissions'      => 'fix/fix_permissions.php',

    // Monitoring & dashboards
    'dashboard'            => 'cli/client_dashboard.php',
    'grafana'              => 'cli/grafana_dashboard.php',
    'client:inventory'     => 'cli/client_inventory.php',
    'client:health-check'  => 'cli/client_health_check.php',

    // Badge & wiki
    'badge:update'         => 'cli/badge_update.php',
    'wiki:sync'            => 'cli/wiki_sync.php',

    // Licensing
    'license'              => 'cli/license_manage.php',

    // Shell completion
    'completion'           => 'cli/completion.php',

    // Module validation
    'validate:module'      => 'bin/validate-module',
];

// ── Argument parsing ─────────────────────────────────────────────────────────

$args    = array_slice($argv, 1);
$command = array_shift($args) ?? '';

// Strip leading -- separator that Composer passes when using `composer run-script cmd -- extra-args`
if (isset($args[0]) && $args[0] === '--') {
    array_shift($args);
}

// ── Help / list ───────────────────────────────────────────────────────────────

if ($command === '' || $command === '--help' || $command === '-h' || $command === 'help') {
    printHelp();
    exit(0);
}

if ($command === 'list' || $command === 'commands') {
    printCommandList();
    exit(0);
}

// ── Dispatch ──────────────────────────────────────────────────────────────────

$scriptRelative = null;

if (array_key_exists($command, COMMAND_MAP)) {
    $scriptRelative = COMMAND_MAP[$command];
} else {
    // Fall back to plugin-provided commands before giving up.
    $pluginCommands = loadPluginCommands();
    if (isset($pluginCommands[$command]) && !empty($pluginCommands[$command]['script'])) {
        $scriptRelative = $pluginCommands[$command]['script'];
    }
}

if ($scriptRelative === null) {
    fwrite(STDERR, "Error: Unknown command '{$command}'\n\n");
    printCommandList();
    exit(2);
}

$scriptPath = $repoRoot . '/' . $scriptRelative;

if (!is_file($scriptPath)) {
    fwrite(STDERR, "Error: Script not found: {$scriptRelative}\n");
    fwrite(STDERR, "Ensure the repository is complete and run: composer install\n");
    exit(2);
}

// Rebuild $argv as if the target script were invoked directly, then include it.
// This is equivalent to: php <script> [args…]  but keeps us in the same process.
$argv  = array_merge([$scriptPath], $args);
$argc  = count($argv);

// Suppress the "run directly" guard that some scripts use (they check realpath($argv[0]) === __FILE__).
// By setting $argv[0] to the script's own path the guard passes naturally.
require $scriptPath;

// ── Helpers ───────────────────────────────────────────────────────────────────

function printHelp(): void
{
    echo <<<'HELP'
╔══════════════════════════════════════════════════════════╗
║              MokoStandards CLI  (bin/moko)               ║
╚══════════════════════════════════════════════════════════╝

Run any MokoStandards script locally without GitHub Actions.

USAGE
  php bin/moko <command> [options]     (all platforms)
  ./bin/moko <command> [options]       (Unix, after: chmod +x bin/moko)

Run `php bin/moko list` to see all available commands.
Run `php bin/moko <command> --help` for command-specific help.

QUICK START
  1. composer install
  2. cp .env.example .env   # add your GH_TOKEN
  3. php bin/moko health    # run full health check

AUTHENTICATION
  GH_TOKEN env var  →  GITHUB_TOKEN env var  →  gh auth login

HELP;
}

function printCommandList(): void
{
    echo "Available commands:\n\n";

    // Auto-group by command prefix or comment-based sections
    $groups = [];
    foreach (COMMAND_MAP as $cmd => $path) {
        if (str_contains($cmd, ':')) {
            $prefix = explode(':', $cmd)[0];
            $groupName = match ($prefix) {
                'check' => 'Validation',
                'version' => 'Version',
                'release' => 'Release',
                'build' => 'Build',
                'platform', 'manifest' => 'Platform',
                'repo' => 'Repository',
                'bulk' => 'Bulk Operations',
                'client' => 'Client Management',
                'validate' => 'Module Validation',
                'deploy' => 'Deploy',
                'fix' => 'Fix / Auto-remediation',
                'maintenance' => 'Maintenance',
                'automation' => 'Automation',
                'badge' => 'Badges',
                'wiki' => 'Wiki',
                default => ucfirst($prefix),
            };
        } else {
            $groupName = match ($cmd) {
                'sync' => 'Automation',
                'inventory' => 'Maintenance',
                'health' => 'Validation',
                'detect', 'drift' => 'Validation',
                'dashboard', 'grafana' => 'Monitoring',
                'release' => 'Release',
                'license' => 'Licensing',
                default => 'Other',
            };
        }
        $groups[$groupName][$cmd] = $path;
    }

    // Load plugin commands
    $pluginCommands = loadPluginCommands();
    if (!empty($pluginCommands)) {
        foreach ($pluginCommands as $cmd => $info) {
            $type = $info['plugin'] ?? 'Plugin';
            $groups["Plugin: {$type}"][$cmd] = $info['description'] ?? '';
        }
    }

    ksort($groups);

    foreach ($groups as $group => $commands) {
        echo "  \033[1m{$group}\033[0m\n";
        ksort($commands);
        foreach ($commands as $cmd => $path) {
            printf("    \033[36m%-26s\033[0m %s\n", $cmd, basename($path));
        }
        echo "\n";
    }

    $total = count(COMMAND_MAP) + count($pluginCommands);
    echo "{$total} command(s) available.\n";
    echo "Run: php bin/moko <command> --help\n";
}

/**
 * Load commands from registered plugins.
 *
 * @return array<string, array{plugin: string, description: string, script: string}>
 */
function loadPluginCommands(): array
{
    $pluginDir = dirname(__DIR__) . '/lib/Enterprise/Plugins';
    if (!is_dir($pluginDir)) {
        return [];
    }

    $commands = [];

    foreach (glob("{$pluginDir}/*Plugin.php") as $file) {
        $className = 'MokoEnterprise\\Plugins\\'
            . pathinfo($file, PATHINFO_FILENAME);

        if (!class_exists($className)) {
            continue;
        }

        try {
            $ref = new \ReflectionClass($className);
            if ($ref->isAbstract()) {
                continue;
            }

            $plugin = $ref->newInstanceWithoutConstructor();
            $pluginCmds = $plugin->getCommands();

            foreach ($pluginCmds as $cmd) {
                $name = $cmd['name'] ?? '';
                if ($name === '') {
                    continue;
                }

                $type = method_exists($plugin, 'getProjectType')
                    ? $plugin->getProjectType() : 'unknown';

                $commands[$name] = [
                    'plugin' => $type,
                    'description' => $cmd['description'] ?? '',
                    'script' => $cmd['script'] ?? '',
                ];
            }
        } catch (\Throwable $e) {
            // Skip plugins that can't be instantiated
            continue;
        }
    }

    return $commands;
}
