<?php namespace EvolutionCMS\Console\Packages;

use Illuminate\Console\Command;
use \EvolutionCMS;
use Illuminate\Support\Facades\File;

/**
 * PackageCommand - Discover and register ServiceProviders from custom packages
 *
 * This command scans composer.json files and generates provider config files
 * in core/custom/config/app/providers/ directory.
 *
 * Provider Priority System:
 * -------------------------
 * Providers can specify load priority in their composer.json:
 *
 * "extra": {
 *     "laravel": {
 *         "providers": ["Vendor\\Package\\SomeServiceProvider"],
 *         "priority": {
 *             "Vendor\\Package\\SomeServiceProvider": 1
 *         }
 *     }
 * }
 *
 * Priority 1-999: Creates file with prefix (e.g., 001_SomeServiceProvider.php)
 * Priority 0 (or not specified): Creates file without prefix
 *
 * Files are loaded in alphabetical order, so:
 * - 001_sLangServiceProvider.php loads first (priority: 1)
 * - 050_SomeOtherProvider.php loads second (priority: 50)
 * - sCommerceServiceProvider.php loads third (priority: 0 or not specified)
 * - etc.
 *
 * @package EvolutionCMS\Console\Packages
 */
class PackageCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'package:discover';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate ServiceProviders for custom packages';
    /**
     * Path for custom providers
     * @var string
     */
    protected $configDir = EVO_CORE_PATH . 'custom/config/app/providers/';
    /**
     * Path for custom aliases
     * @var string
     */
    protected $aliasesDir = EVO_CORE_PATH . 'custom/config/app/aliases/';
    /**
     * Track aliases generated during current discovery run.
     * @var array<string,bool>
     */
    protected $discoveredAliasFiles = [];

    /**
     * Marker used to identify auto-generated alias files.
     * @var string
     */
    protected $aliasMarker = 'Generated by package:discover';
    /**
     * Custom composer.json
     * @var string
     */
    protected $composer = EVO_CORE_PATH . 'custom/composer.json';
    /**
     * @var string
     */
    public $packagePath = '';

    /**
     * @var mixed|string
     */
    public $load_dir = '';

    /**
     * @var \DocumentParser|string
     */
    public $evo = '';

    /**
     * @var array
     */
    public $require = [];

    /**
     * PackageCommand constructor.
     */
    public function __construct()
    {
        parent::__construct();
        $this->evo = evo();
        $this->load_dir = $this->evo->getConfig('rb_base_dir');
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (!is_dir($this->configDir)) {
            mkdir($this->configDir, 0775, true);
        }
        if (!is_dir($this->configDir)) {
            $this->getOutput()->write('<error>ERROR CREATE CONFIG DIR</error>');
            exit();
        }

        if (!is_dir($this->aliasesDir)) {
            mkdir($this->aliasesDir, 0775, true);
        }
        if (!is_dir($this->aliasesDir)) {
            $this->getOutput()->write('<error>ERROR CREATE ALIASES DIR</error>');
            exit();
        }

        if (file_exists($this->composer)) {
            $this->parseComposer($this->composer);
        }
        if (count($this->require) > 0) {
            $this->loadRequire();
        }

        $this->cleanupAliases();

        unlink(EVO_CORE_PATH . 'storage/bootstrap/services.php');
    }

    /**
     * @param string $composer
     */
    public function parseComposer(string $composer)
    {
        $data = json_decode(file_get_contents($composer), true);
        if (isset($data['extra']['laravel']['providers'])) {
            // Get priorities if defined (default to empty array if not present)
            $priorities = $data['extra']['laravel']['priority'] ?? [];

            foreach ($data['extra']['laravel']['providers'] as $value) {
                // Get priority for this provider (default 0 if not specified)
                $priority = $priorities[$value] ?? 0;
                $this->process($value, $priority);
            }
        }

        if (isset($data['extra']['laravel']['aliases']) && is_array($data['extra']['laravel']['aliases'])) {
            foreach ($data['extra']['laravel']['aliases'] as $alias => $class) {
                $this->processAlias((string) $alias, (string) $class);
            }
        }

        if (isset($data['require'])) {
            foreach ($data['require'] as $key => $value) {
                $composer = EVO_CORE_PATH . 'vendor/' . $key . '/composer.json';
                $this->packagePath = EVO_CORE_PATH . 'vendor/' . $key . '/';
                if (file_exists($composer)) {
                    $this->checkRequired($composer);
                    $this->parseComposerServiceProvider($composer);
                }
            }
        }
        if (isset($data['autoload']['psr-4'])) {
            foreach ($data['autoload']['psr-4'] as $key => $value) {
                $composer = EVO_CORE_PATH . 'custom/' . $value . '/composer.json';
                $this->packagePath = EVO_CORE_PATH . 'custom/' . $value . '/';
                if (file_exists($composer)) {
                    $this->parseComposerServiceProvider($composer);
                }

            }
        }
    }

    /**
     * @param string $composer
     */
    public function parseComposerServiceProvider(string $composer)
    {
        $data = json_decode(file_get_contents($composer), true);
        if (isset($data['extra']['laravel']['providers']) && is_array($data['extra']['laravel']['providers'])) {
            // Get priorities if defined (default to empty array if not present)
            $priorities = $data['extra']['laravel']['priority'] ?? [];

            foreach ($data['extra']['laravel']['providers'] as $value) {
                // Get priority for this provider (default 0 if not specified)
                $priority = $priorities[$value] ?? 0;
                $this->process($value, $priority);
            }
        }

        if (isset($data['extra']['laravel']['aliases']) && is_array($data['extra']['laravel']['aliases'])) {
            foreach ($data['extra']['laravel']['aliases'] as $alias => $class) {
                $this->processAlias((string) $alias, (string) $class);
            }
        }

        if (isset($data['extra']['laravel']['files'])) {
            foreach ($data['extra']['laravel']['files'] as $copyArray) {
                $this->copyFiles($copyArray);
            }
        }
    }

    /**
     * Generate provider config file with optional priority prefix
     *
     * @param string $value Provider class name
     * @param int $priority Priority (1-999), used for load order. Lower numbers load first.
     */
    protected function process(string $value, int $priority = 0)
    {
        $arrNamespace = explode('\\', $value);
        $className = end($arrNamespace);

        // Add priority prefix if specified (001_, 002_, ..., 999_)
        if ($priority > 0 && $priority < 1000) {
            $prefix = str_pad($priority, 3, '0', STR_PAD_LEFT) . '_';
            $fileName = $prefix . $className . '.php';
            $fileContent = "<?php\n// Priority {$priority} - loads before other providers\nreturn " . $value . "::class;";

            // Remove old files with different prefixes for the same provider
            $patterns = [
                $this->configDir . $className . '.php',                    // without prefix
                $this->configDir . '*_' . $className . '.php',             // with any prefix
            ];

            foreach ($patterns as $pattern) {
                foreach (glob($pattern) as $oldFile) {
                    if ($oldFile !== $this->configDir . $fileName) {
                        @unlink($oldFile);
                        $this->getOutput()->write('<comment>Removed old ' . basename($oldFile) . ' (using priority prefix)</comment>');
                    }
                }
            }
        } else {
            $fileName = $className . '.php';
            $fileContent = "<?php \nreturn " . $value . "::class;";
        }

        if (file_put_contents($this->configDir . $fileName, $fileContent)) {
            $this->getOutput()->write('<info>' . $value . ($priority > 0 ? " (priority: {$priority})" : '') . '</info>');
        } else {
            $this->getOutput()->write('<error>Error create config for: ' . $value . '</error>');
        }
        $this->line('');
    }

    /**
     * Generate alias config file for custom packages.
     *
     * Creates core/custom/config/app/aliases/<Alias>.php which returns FQCN::class.
     */
    protected function processAlias(string $alias, string $class): void
    {
        $alias = trim($alias);
        $class = trim($class);

        if ($alias === '' || $class === '') {
            return;
        }

        // Keep it safe for filesystem (e.g. "sApi" -> "sApi.php")
        $fileName = preg_replace('~[^A-Za-z0-9_]+~', '', $alias) . '.php';
        if ($fileName === '.php') {
            return;
        }

        $this->discoveredAliasFiles[$fileName] = true;

        $content = "<?php\n// {$this->aliasMarker}\nreturn {$class}::class;\n";
        @file_put_contents($this->aliasesDir . $fileName, $content);
    }

    /**
     * Remove auto-generated alias files that are no longer discovered.
     *
     * This makes aliases self-healing when packages are removed.
     */
    protected function cleanupAliases(): void
    {
        foreach (glob($this->aliasesDir . '*.php') as $file) {
            $baseName = basename($file);
            if (isset($this->discoveredAliasFiles[$baseName])) {
                continue;
            }

            $contents = @file_get_contents($file);
            if (!is_string($contents) || $contents === '') {
                continue;
            }

            if (strpos($contents, $this->aliasMarker) === false) {
                continue;
            }

            @unlink($file);
        }
    }

    /**
     * @param array $copyArray
     */
    protected function copyFiles(array $copyArray)
    {
        File::copyDirectory($this->packagePath . $copyArray['source'], $this->load_dir . $copyArray['destination']);
    }

    protected function checkRequired($composer)
    {
        $composerArray = json_decode(file_get_contents($composer), true);
        if (isset($composerArray['require']) && is_array($composerArray['require'])) {
            foreach ($composerArray['require'] as $key => $item) {
                $this->require[] = $key;
            }
        }
    }

    protected function loadRequire()
    {
        foreach ($this->require as $require) {
            $composer = EVO_CORE_PATH . 'vendor/' . $require . '/composer.json';
            $this->packagePath = EVO_CORE_PATH . 'vendor/' . $require . '/';
            if (file_exists($composer)) {
                $this->parseComposerServiceProvider($composer);
            }
        }
    }
}
