#!/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
 *   sync                  Bulk-sync MokoStandards to organisation repos
 *   health                Full repository health check (runs most validators)
 *   inventory             Refresh docs/reference/REPOSITORY_INVENTORY.md
 *
 *   check:syntax          PHP syntax check (php -l) on all tracked .php files
 *   check:version         Verify VERSION fields and badges match composer.json
 *   check:changelog       Validate CHANGELOG.md format
 *   check:structure       Verify required root files and directories
 *   check:headers         Check SPDX-License-Identifier presence in source files
 *   check:secrets         Scan for leaked credentials / API keys
 *   check:tabs            Detect tab characters in YAML files
 *   check:paths           Detect backslash path separators in PHP source
 *   check:xml             Validate XML files are well-formed
 *   check:enterprise      Full enterprise-readiness check (headers, strict types, PSR-12)
 *   check:dolibarr        Validate Dolibarr module directory structure
 *   check:joomla          Validate Joomla XML manifest
 *   check:language        Validate Joomla/Dolibarr .ini language files
 *   detect                Auto-detect repository platform type
 *   drift                 Scan org repos for drift from MokoStandards templates
 *
 * 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 = [
    // Automation
    'sync'             => 'automation/bulk_sync.php',

    // Maintenance
    'inventory'        => 'maintenance/update_repo_inventory.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:language'   => 'validate/check_language_structure.php',
    'check:client'     => 'validate/check_client_theme.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',
    'manifest:element'     => 'cli/manifest_element.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',

    // 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',

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

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

    // Repository management
    'repo:create'          => 'cli/create_repo.php',
    'repo:archive'         => 'cli/archive_repo.php',
    'repo:scaffold-client' => 'cli/scaffold_client.php',
    'repo:provision'       => 'cli/client_provision.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',

    // Monitoring & dashboards
    'dashboard'            => 'cli/client_dashboard.php',
    'grafana'              => 'cli/grafana_dashboard.php',
    'client:inventory'     => 'cli/client_inventory.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 ──────────────────────────────────────────────────────────────────

if (!array_key_exists($command, COMMAND_MAP)) {
    fwrite(STDERR, "Error: Unknown command '{$command}'\n\n");
    printCommandList();
    exit(2);
}

$scriptPath = $repoRoot . '/' . COMMAND_MAP[$command];

if (!is_file($scriptPath)) {
    fwrite(STDERR, "Error: Script not found: " . COMMAND_MAP[$command] . "\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',
                default => ucfirst($prefix),
            };
        } else {
            $groupName = match ($cmd) {
                'sync' => 'Automation',
                'inventory' => 'Maintenance',
                'health' => 'Validation',
                'detect', 'drift' => 'Validation',
                'dashboard', 'grafana' => 'Monitoring',
                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;
}
