<?php
declare(strict_types=1);

namespace Celena\Core\Plugin;

use Celena\Core\Container;

/**
 * Сканирует /plugins, читает manifest.json, подключает plugin.php у активных плагинов.
 * Список активных хранится в таблице celena_options (ключ: active_plugins).
 */
final class PluginManager
{
    /** @var array<string, array<string, mixed>> */
    private array $plugins = [];

    /** @var string[] */
    private array $active = [];

    public function __construct(
        private readonly string $pluginsPath,
        private readonly Container $container,
    ) {
    }

    public function discover(): void
    {
        $this->plugins = [];
        foreach (glob($this->pluginsPath . '/*', GLOB_ONLYDIR) ?: [] as $dir) {
            $manifestFile = $dir . '/manifest.json';
            if (!is_file($manifestFile)) {
                continue;
            }
            $manifest = json_decode((string) file_get_contents($manifestFile), true);
            if (!is_array($manifest) || empty($manifest['slug'])) {
                continue;
            }
            $manifest['path'] = $dir;
            $this->plugins[(string) $manifest['slug']] = $manifest;
        }
    }

    /** @param string[] $activeSlugs */
    public function load(array $activeSlugs): void
    {
        if ($this->plugins === []) {
            $this->discover();
        }
        $this->active = [];
        foreach ($activeSlugs as $slug) {
            if (!isset($this->plugins[$slug])) {
                continue;
            }
            $pluginPath = (string) $this->plugins[$slug]['path'];
            // Регистрируем namespace плагина: Celena\Plugin\<Slug>\ → plugins/<slug>/src/
            $srcDir = $pluginPath . '/src';
            if (is_dir($srcDir) && \Celena\Core\Autoloader::instance() !== null) {
                \Celena\Core\Autoloader::instance()->addPrefix(
                    'Celena\\Plugin\\' . ucfirst($slug) . '\\',
                    $srcDir
                );
            }

            $bootstrap = $pluginPath . '/plugin.php';
            if (is_file($bootstrap)) {
                $container = $this->container;
                (function () use ($bootstrap, $container) {
                    require $bootstrap;
                })();
            }
            // Синхронизация ассетов плагина в /public/assets/plugins/<slug>/ если они изменились.
            $this->syncAssets($slug, $pluginPath);
            $this->active[] = $slug;
        }
    }

    /** @return string[] */
    public function activeAssetUrls(): array
    {
        $urls = [];
        foreach ($this->active as $slug) {
            $cssFile = dirname($this->pluginsPath) . '/public/assets/plugins/' . $slug . '/' . $slug . '.css';
            if (is_file($cssFile)) {
                $urls[] = '/public/assets/plugins/' . $slug . '/' . $slug . '.css';
            }
        }
        return $urls;
    }

    /** @return string[] */
    public function activeScriptUrls(): array
    {
        $urls = [];
        foreach ($this->active as $slug) {
            $jsFile = dirname($this->pluginsPath) . '/public/assets/plugins/' . $slug . '/' . $slug . '.js';
            if (is_file($jsFile)) {
                $urls[] = '/public/assets/plugins/' . $slug . '/' . $slug . '.js';
            }
        }
        return $urls;
    }

    private function syncAssets(string $slug, string $pluginDir): void
    {
        $src = $pluginDir . '/assets';
        if (!is_dir($src)) return;
        $dst = dirname($this->pluginsPath) . '/public/assets/plugins/' . $slug;
        if (!is_dir($dst)) @mkdir($dst, 0755, true);
        foreach (glob($src . '/*') ?: [] as $file) {
            if (!is_file($file)) continue;
            $target = $dst . '/' . basename($file);
            if (!is_file($target) || filemtime($file) > filemtime($target)) {
                @copy($file, $target);
            }
        }
    }

    /** @return array<string, array<string, mixed>> */
    public function all(): array { return $this->plugins; }

    /** @return string[] */
    public function active(): array { return $this->active; }

    /**
     * Список разделов документации плагина. Источник:
     *   1. manifest.docs (если задано) — массив { file, title }
     *   2. иначе — все *.md файлы из plugins/<slug>/docs/, README первым
     *
     * Заголовок секции берётся из manifest или из первой `# H1` строки .md
     * (или базовое имя файла без расширения как fallback).
     *
     * @return array<int, array{slug:string, file:string, title:string, abs_path:string}>
     */
    public function docsFor(string $slug): array
    {
        $slug = trim($slug);
        if ($slug === '' || !preg_match('/^[a-z0-9_\-]+$/', $slug)) {
            return [];
        }
        $docsDir = $this->pluginsPath . '/' . $slug . '/docs';
        if (!is_dir($docsDir)) {
            return [];
        }

        $manifest = $this->plugins[$slug] ?? null;
        $declared = is_array($manifest) ? ($manifest['docs'] ?? null) : null;

        $items = [];
        if (is_array($declared) && $declared !== []) {
            foreach ($declared as $entry) {
                if (!is_array($entry) || empty($entry['file'])) continue;
                $file = basename((string) $entry['file']);
                if (!preg_match('/\.md$/i', $file)) continue;
                $abs = $docsDir . '/' . $file;
                if (!is_file($abs)) continue;
                $items[] = [
                    'slug'     => $this->docSlug($file),
                    'file'     => $file,
                    'title'    => (string) ($entry['title'] ?? $this->extractTitle($abs, $file)),
                    'abs_path' => $abs,
                ];
            }
            return $items;
        }

        // Auto-scan: все .md, README первым
        $files = glob($docsDir . '/*.md') ?: [];
        usort($files, function (string $a, string $b): int {
            $ba = strtolower(basename($a));
            $bb = strtolower(basename($b));
            if ($ba === 'readme.md') return -1;
            if ($bb === 'readme.md') return 1;
            return strcmp($ba, $bb);
        });
        foreach ($files as $abs) {
            $file = basename($abs);
            $items[] = [
                'slug'     => $this->docSlug($file),
                'file'     => $file,
                'title'    => $this->extractTitle($abs, $file),
                'abs_path' => $abs,
            ];
        }
        return $items;
    }

    private function docSlug(string $file): string
    {
        $base = preg_replace('/\.md$/i', '', $file) ?? $file;
        return strtolower(str_replace([' ', '_'], '-', $base));
    }

    private function extractTitle(string $absPath, string $fallbackFile): string
    {
        $h = @fopen($absPath, 'rb');
        if ($h !== false) {
            $linesRead = 0;
            while (($line = fgets($h)) !== false && $linesRead < 30) {
                $linesRead++;
                $line = trim($line);
                if ($line === '') continue;
                if (preg_match('/^#\s+(.+)$/u', $line, $m)) {
                    fclose($h);
                    return trim($m[1]);
                }
                // Первая непустая не-заголовочная строка — прерываемся
                if ($line[0] !== '#') break;
            }
            fclose($h);
        }
        return preg_replace('/\.md$/i', '', $fallbackFile) ?? $fallbackFile;
    }
}
