<?php

namespace MintyPHP\Console\Commands\Make;

use MintyPHP\Console\Command;

/**
 * Scaffolds a new module with manifest, registrar, policy, and directory structure.
 *
 * Usage: php bin/console make:module <id>
 * Example: php bin/console make:module notifications
 *
 * Generates:
 *   modules/<id>/module.php
 *   modules/<id>/lib/Module/<Name>/<Name>ContainerRegistrar.php
 *   modules/<id>/lib/Module/<Name>/<Name>AuthorizationPolicy.php
 *   modules/<id>/pages/
 *   modules/<id>/templates/
 *   modules/<id>/web/css/
 *   modules/<id>/web/js/
 *   modules/<id>/tests/Module/<Name>/
 */
final class ModuleCommand extends Command
{
    public function name(): string
    {
        return 'make:module';
    }

    public function description(): string
    {
        return 'Scaffold a new module with manifest and stub classes';
    }

    public function usage(): string
    {
        return <<<'USAGE'
        Usage: php bin/console make:module <module-id> [--simple]

        Arguments:
          module-id    Lowercase identifier (e.g. notifications, activity-log)
                       Must match [a-z][a-z0-9-]* pattern.

        Options:
          --simple     Generate a minimal manifest with only essential fields.
                       Ideal for lightweight UI-only modules.

        Example:
          php bin/console make:module notifications
          php bin/console make:module help-panel --simple

        Generates the full module directory structure with:
          - module.php manifest (all fields or minimal with --simple)
          - ContainerRegistrar stub
          - AuthorizationPolicy stub
          - Empty pages/, templates/, web/css/, web/js/, tests/ directories
        USAGE;
    }

    public function execute(array $args, array $options): int
    {
        $moduleId = trim($args[0] ?? '');

        if ($moduleId === '') {
            fwrite(STDERR, $this->usage() . "\n");
            return 1;
        }

        if (!preg_match('/^[a-z][a-z0-9-]*$/', $moduleId)) {
            fwrite(STDERR, "Error: module ID must match [a-z][a-z0-9-]* (got '{$moduleId}')\n");
            return 1;
        }

        $modulesDir = $this->projectRoot() . '/modules';
        $moduleDir = $modulesDir . '/' . $moduleId;

        if (is_dir($moduleDir)) {
            fwrite(STDERR, "Error: module directory already exists: modules/{$moduleId}/\n");
            return 1;
        }

        $pascalName = self::toPascalCase($moduleId);
        $snakeName = str_replace('-', '_', $moduleId);

        // Create directories
        $dirs = [
            "lib/Module/{$pascalName}",
            'pages',
            'templates',
            'web/css',
            'web/js',
            "tests/Module/{$pascalName}",
        ];

        foreach ($dirs as $dir) {
            $path = $moduleDir . '/' . $dir;
            if (!mkdir($path, 0777, true) && !is_dir($path)) {
                fwrite(STDERR, "Error: failed to create directory: {$dir}\n");
                return 1;
            }
        }

        // Generate files
        $simple = $options['simple'] ?? false;
        $files = [
            'module.php' => $simple
                ? self::simpleManifestTemplate($moduleId, $pascalName)
                : self::manifestTemplate($moduleId, $pascalName, $snakeName),
            "lib/Module/{$pascalName}/{$pascalName}ContainerRegistrar.php" => self::registrarTemplate($pascalName),
            "lib/Module/{$pascalName}/{$pascalName}AuthorizationPolicy.php" => self::policyTemplate($pascalName, $snakeName),
        ];

        foreach ($files as $relativePath => $content) {
            $fullPath = $moduleDir . '/' . $relativePath;
            if (file_put_contents($fullPath, $content) === false) {
                fwrite(STDERR, "Error: failed to write file: {$relativePath}\n");
                return 1;
            }
        }

        // Add .gitkeep to empty directories
        foreach (['pages', 'templates', 'web/css', 'web/js', "tests/Module/{$pascalName}"] as $dir) {
            touch($moduleDir . '/' . $dir . '/.gitkeep');
        }

        fwrite(STDOUT, "Module '{$moduleId}' scaffolded at modules/{$moduleId}/\n\n");
        fwrite(STDOUT, "Created:\n");
        fwrite(STDOUT, "  modules/{$moduleId}/module.php\n");
        fwrite(STDOUT, "  modules/{$moduleId}/lib/Module/{$pascalName}/{$pascalName}ContainerRegistrar.php\n");
        fwrite(STDOUT, "  modules/{$moduleId}/lib/Module/{$pascalName}/{$pascalName}AuthorizationPolicy.php\n");
        fwrite(STDOUT, "  modules/{$moduleId}/pages/\n");
        fwrite(STDOUT, "  modules/{$moduleId}/templates/\n");
        fwrite(STDOUT, "  modules/{$moduleId}/web/css/\n");
        fwrite(STDOUT, "  modules/{$moduleId}/web/js/\n");
        fwrite(STDOUT, "  modules/{$moduleId}/tests/Module/{$pascalName}/\n");
        fwrite(STDOUT, "\nNext steps:\n");
        fwrite(STDOUT, "  1. Add '{$moduleId}' to config/modules.php enabled_modules\n");
        fwrite(STDOUT, "  2. Register the AuthorizationPolicy in {$pascalName}ContainerRegistrar (required for UI slot visibility)\n");
        fwrite(STDOUT, "  3. Edit module.php to define routes, permissions, and UI slots\n");
        fwrite(STDOUT, "  4. Run: php bin/console module:sync\n");
        fwrite(STDOUT, "\nGuides:\n");
        fwrite(STDOUT, "  Tutorial:          docs/howto-modul-erstellen.md\n");
        fwrite(STDOUT, "  Manifest contract: docs/reference-module-manifest-contract.md\n");
        fwrite(STDOUT, "  Reference module:  modules/notifications/ (full-featured example)\n");

        return 0;
    }

    private static function toPascalCase(string $id): string
    {
        return str_replace(' ', '', ucwords(str_replace('-', ' ', $id)));
    }

    private static function manifestTemplate(string $id, string $pascalName, string $snakeName): string
    {
        $registrarClass = "\\MintyPHP\\Module\\{$pascalName}\\{$pascalName}ContainerRegistrar::class";
        $policyClass = "\\MintyPHP\\Module\\{$pascalName}\\{$pascalName}AuthorizationPolicy::class";

        return <<<PHP
        <?php

        /**
         * {$pascalName} module manifest.
         */
        return [
            'id' => '{$id}',
            'version' => '0.1.0',
            'enabled_by_default' => false,
            'load_order' => 100,
            'requires' => [],

            'routes' => [
                // ['path' => '{$id}', 'target' => '{$id}'],
            ],
            'public_paths' => [],
            'container_registrars' => [
                {$registrarClass},
            ],

            'ui_slots' => [],

            'authorization_policies' => [
                {$policyClass},
            ],

            'search_resources' => [],
            'asset_groups' => [],
            'scheduler_jobs' => [],

            'layout_context_providers' => [],
            'session_providers' => [],

            'permissions' => [
                // [
                //     'key' => '{$snakeName}.view',
                //     'description' => 'Can view {$id}',
                //     'active' => true,
                //     'is_system' => true,
                // ],
            ],

            'event_listeners' => [],
            'deactivation_handler' => null,
            'migrations_path' => null,
        ];

        PHP;
    }

    private static function simpleManifestTemplate(string $id, string $pascalName): string
    {
        $registrarClass = "\\MintyPHP\\Module\\{$pascalName}\\{$pascalName}ContainerRegistrar::class";
        $policyClass = "\\MintyPHP\\Module\\{$pascalName}\\{$pascalName}AuthorizationPolicy::class";

        return <<<PHP
        <?php

        /**
         * {$pascalName} module manifest (minimal).
         *
         * Extend with routes, permissions, ui_slots, etc. as needed.
         * See docs/howto-modul-erstellen.md for all available fields.
         */
        return [
            'id' => '{$id}',
            'version' => '0.1.0',
            'enabled_by_default' => false,
            'load_order' => 100,
            'requires' => [],
            'container_registrars' => [
                {$registrarClass},
            ],
            'authorization_policies' => [
                {$policyClass},
            ],
        ];

        PHP;
    }

    private static function registrarTemplate(string $pascalName): string
    {
        return <<<PHP
        <?php

        namespace MintyPHP\\Module\\{$pascalName};

        use MintyPHP\\App\\AppContainer;
        use MintyPHP\\App\\Container\\ContainerRegistrar;

        final class {$pascalName}ContainerRegistrar implements ContainerRegistrar
        {
            public function register(AppContainer \$container): void
            {
                // Register module services here.
                // Example:
                // \$container->set(MyService::class, static fn (AppContainer \$c): MyService => new MyService(
                //     \$c->get(SomeDependency::class)
                // ));
            }
        }

        PHP;
    }

    private static function policyTemplate(string $pascalName, string $snakeName): string
    {
        return <<<PHP
        <?php

        namespace MintyPHP\\Module\\{$pascalName};

        use MintyPHP\\Service\\Access\\AuthorizationDecision;
        use MintyPHP\\Service\\Access\\AuthorizationPolicyInterface;
        use MintyPHP\\Service\\Access\\PermissionService;

        final class {$pascalName}AuthorizationPolicy implements AuthorizationPolicyInterface
        {
            public const ABILITY_VIEW = '{$snakeName}.view';
            public const PERMISSION_KEY = '{$snakeName}.view';

            public function __construct(
                private readonly PermissionService \$permissionService
            ) {
            }

            public function supports(string \$ability): bool
            {
                return \$ability === self::ABILITY_VIEW;
            }

            public function authorize(string \$ability, array \$context = []): AuthorizationDecision
            {
                \$actorUserId = (int) (\$context['actor_user_id'] ?? 0);
                if (\$actorUserId <= 0) {
                    return AuthorizationDecision::deny(403, 'forbidden');
                }

                if (!\$this->permissionService->userHas(\$actorUserId, self::PERMISSION_KEY)) {
                    return AuthorizationDecision::deny(403, 'forbidden');
                }

                return AuthorizationDecision::allow();
            }
        }

        PHP;
    }
}
