<?php

/**
 * PluginManager — WordPress-style plugin engine for SeedProject.
 *
 * Plugins live in plugins/{slug}/ and are integrated via symlinks into
 * public/controllers/, public/views/plugins/, and public/assets/plugins/.
 * No core framework files need modification beyond a single boot() call.
 */
class PluginManager {

    private static string $pluginsDir;
    private static string $controllersDir;
    private static string $viewsDir;
    private static string $assetsDir;

    private static function init(): void {
        static $initialized = false;
        if ($initialized) return;
        $base = dirname(__DIR__, 2); // project root
        self::$pluginsDir     = $base . '/plugins';
        self::$controllersDir = $base . '/public/controllers';
        self::$viewsDir       = $base . '/public/views/plugins';
        self::$assetsDir      = $base . '/public/assets/plugins';
        $initialized = true;
    }

    // ─── Boot (called once per request from Router::Routing) ─────────────────

    /**
     * Register AltoRouter routes for all enabled plugins.
     * Called from Router::Routing() before $router->match().
     */
    public static function boot(AltoRouter $router): void {
        self::init();

        $plugins = Db::select(
            "SELECT slug FROM sp_plugins WHERE enabled = 1",
            []
        );

        if (!$plugins) return;

        foreach ($plugins as $row) {
            $slug       = $row['slug'];
            $manifestPath = self::$pluginsDir . '/' . $slug . '/plugin.php';
            if (!file_exists($manifestPath)) continue;

            $manifest = require $manifestPath;
            foreach ($manifest['routes'] ?? [] as $rule) {
                // Each rule: [method, pattern, target, name]
                if (count($rule) >= 3) {
                    $router->map($rule[0], $rule[1], $rule[2], $rule[3] ?? null);
                }
            }
        }
    }

    // ─── Install ──────────────────────────────────────────────────────────────

    /**
     * Install a plugin: create symlinks, run install.php, upsert DB row (enabled=0).
     */
    public static function install(string $slug): array {
        self::init();

        $slug = self::sanitizeSlug($slug);
        if (!$slug) return ['success' => false, 'error' => 'Invalid slug'];

        $pluginDir = self::$pluginsDir . '/' . $slug;
        if (!is_dir($pluginDir)) {
            return ['success' => false, 'error' => "Plugin directory not found: {$slug}"];
        }

        $manifestPath = $pluginDir . '/plugin.php';
        if (!file_exists($manifestPath)) {
            return ['success' => false, 'error' => "plugin.php manifest missing for: {$slug}"];
        }

        $manifest = require $manifestPath;

        // Create symlinks
        $symlinkResult = self::createSymlinks($slug, $manifest);
        if (!$symlinkResult['success']) return $symlinkResult;

        // Run install.php
        $installScript = $pluginDir . '/install.php';
        if (file_exists($installScript)) {
            try {
                require $installScript;
            } catch (Throwable $e) {
                return ['success' => false, 'error' => 'install.php failed: ' . $e->getMessage()];
            }
        }

        // Upsert sp_plugins row (enabled=0 — admin must explicitly enable)
        $existing = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]);
        if ($existing) {
            Db::update('sp_plugins', [
                'name'        => $manifest['name']        ?? $slug,
                'version'     => $manifest['version']     ?? '1.0.0',
                'description' => $manifest['description'] ?? '',
                'author'      => $manifest['author']      ?? '',
                'updated_at'  => date('Y-m-d H:i:s'),
            ], 'slug = ?', [$slug]);
        } else {
            Db::insert('sp_plugins', [
                'slug'         => $slug,
                'name'         => $manifest['name']        ?? $slug,
                'version'      => $manifest['version']     ?? '1.0.0',
                'description'  => $manifest['description'] ?? '',
                'author'       => $manifest['author']      ?? '',
                'enabled'      => 0,
                'installed_at' => date('Y-m-d H:i:s'),
                'updated_at'   => date('Y-m-d H:i:s'),
            ]);
        }

        return ['success' => true, 'message' => "Plugin '{$slug}' installed. Enable it to activate."];
    }

    // ─── Enable ───────────────────────────────────────────────────────────────

    /**
     * Enable a plugin: ensure symlinks exist, set enabled=1.
     */
    public static function enable(string $slug): array {
        self::init();

        $slug = self::sanitizeSlug($slug);
        $row  = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]);
        if (!$row) return ['success' => false, 'error' => "Plugin '{$slug}' is not installed"];

        $manifest = self::loadManifest($slug);
        if (!$manifest) return ['success' => false, 'error' => "Cannot load manifest for '{$slug}'"];

        $symlinkResult = self::createSymlinks($slug, $manifest);
        if (!$symlinkResult['success']) return $symlinkResult;

        Db::update('sp_plugins', ['enabled' => 1, 'updated_at' => date('Y-m-d H:i:s')], 'slug = ?', [$slug]);

        return ['success' => true, 'message' => "Plugin '{$slug}' enabled"];
    }

    // ─── Disable ──────────────────────────────────────────────────────────────

    /**
     * Disable a plugin: remove symlinks, set enabled=0.
     * Symlink removal causes natural 404 without any guard code.
     */
    public static function disable(string $slug): array {
        self::init();

        $slug = self::sanitizeSlug($slug);
        $row  = Db::getRow("SELECT plugin_id FROM sp_plugins WHERE slug = ?", [$slug]);
        if (!$row) return ['success' => false, 'error' => "Plugin '{$slug}' is not installed"];

        self::removeSymlinks($slug);

        Db::update('sp_plugins', ['enabled' => 0, 'updated_at' => date('Y-m-d H:i:s')], 'slug = ?', [$slug]);

        return ['success' => true, 'message' => "Plugin '{$slug}' disabled"];
    }

    // ─── Uninstall ────────────────────────────────────────────────────────────

    /**
     * Uninstall a plugin: remove symlinks, run uninstall.php, delete DB row.
     */
    public static function uninstall(string $slug): array {
        self::init();

        $slug = self::sanitizeSlug($slug);

        self::removeSymlinks($slug);

        $uninstallScript = self::$pluginsDir . '/' . $slug . '/uninstall.php';
        if (file_exists($uninstallScript)) {
            try {
                require $uninstallScript;
            } catch (Throwable $e) {
                // Log but don't abort — clean up DB anyway
            }
        }

        Db::delete('sp_plugins', ['slug' => $slug]);

        return ['success' => true, 'message' => "Plugin '{$slug}' uninstalled"];
    }

    // ─── Discover ─────────────────────────────────────────────────────────────

    /**
     * Scan plugins/ directory and merge with DB rows.
     * Returns array of plugin info for the admin UI.
     */
    public static function discover(): array {
        self::init();

        // Get all DB rows keyed by slug
        $dbRows = [];
        $rows = Db::select("SELECT * FROM sp_plugins", []);
        foreach ($rows as $row) {
            $dbRows[$row['slug']] = $row;
        }

        $plugins = [];

        if (!is_dir(self::$pluginsDir)) return $plugins;

        $dirs = glob(self::$pluginsDir . '/*/plugin.php');
        if (!$dirs) return $plugins;

        foreach ($dirs as $manifestPath) {
            $slug = basename(dirname($manifestPath));
            $manifest = require $manifestPath;
            $dbRow = $dbRows[$slug] ?? null;

            $plugins[] = [
                'slug'         => $slug,
                'name'         => $manifest['name']        ?? $slug,
                'version'      => $manifest['version']     ?? '1.0.0',
                'description'  => $manifest['description'] ?? '',
                'author'       => $manifest['author']      ?? '',
                'installed'    => $dbRow !== null,
                'enabled'      => (bool)($dbRow['enabled'] ?? false),
                'installed_at' => $dbRow['installed_at']   ?? null,
                'plugin_id'    => $dbRow['plugin_id']      ?? null,
            ];
        }

        return $plugins;
    }

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

    public static function pluginPath(string $slug): string {
        self::init();
        return self::$pluginsDir . '/' . $slug;
    }

    private static function sanitizeSlug(string $slug): string {
        return preg_replace('/[^a-z0-9_-]/', '', strtolower(trim($slug)));
    }

    private static function loadManifest(string $slug): ?array {
        self::init();
        $path = self::$pluginsDir . '/' . $slug . '/plugin.php';
        return file_exists($path) ? require $path : null;
    }

    /**
     * Copy plugin files into the framework directories.
     * public/controllers/{slug}.php  ← plugins/{slug}/{Slug}Controller.php
     * public/views/plugins/{slug}/   ← plugins/{slug}/views/{slug}/
     * public/assets/plugins/{slug}/  ← plugins/{slug}/assets/
     */
    private static function createSymlinks(string $slug, array $manifest): array {
        self::init();

        $pluginDir       = self::$pluginsDir . '/' . $slug;
        $controllerClass = ucfirst($slug) . 'Controller';

        // Ensure parent dirs exist
        foreach ([self::$viewsDir, self::$assetsDir] as $dir) {
            if (!is_dir($dir)) {
                mkdir($dir, 0755, true);
            }
        }

        // Copy controller file
        $controllerSrc  = $pluginDir . '/' . $controllerClass . '.php';
        $controllerDest = self::$controllersDir . '/' . $slug . '.php';
        if (!file_exists($controllerSrc)) {
            return ['success' => false, 'error' => "Controller not found: {$controllerClass}.php"];
        }
        if (!copy($controllerSrc, $controllerDest)) {
            return ['success' => false, 'error' => "Failed to copy controller to: {$controllerDest}"];
        }

        // Copy views directory (optional)
        $viewsSrc  = $pluginDir . '/views/' . $slug;
        $viewsDest = self::$viewsDir . '/' . $slug;
        if (is_dir($viewsSrc)) {
            $result = self::copyDir($viewsSrc, $viewsDest);
            if (!$result) {
                return ['success' => false, 'error' => "Failed to copy views to: {$viewsDest}"];
            }
        }

        // Copy assets directory (optional)
        $assetsSrc  = $pluginDir . '/assets';
        $assetsDest = self::$assetsDir . '/' . $slug;
        if (is_dir($assetsSrc)) {
            $result = self::copyDir($assetsSrc, $assetsDest);
            if (!$result) {
                return ['success' => false, 'error' => "Failed to copy assets to: {$assetsDest}"];
            }
        }

        return ['success' => true];
    }

    /**
     * Remove copied plugin files from framework directories.
     */
    private static function removeSymlinks(string $slug): void {
        self::init();

        $controllerFile = self::$controllersDir . '/' . $slug . '.php';
        if (file_exists($controllerFile)) {
            unlink($controllerFile);
        }

        foreach ([self::$viewsDir . '/' . $slug, self::$assetsDir . '/' . $slug] as $dir) {
            if (is_dir($dir)) {
                self::removeDir($dir);
            }
        }
    }

    /**
     * Recursively copy a directory.
     */
    private static function copyDir(string $src, string $dest): bool {
        if (!is_dir($dest)) {
            mkdir($dest, 0755, true);
        }
        foreach (scandir($src) as $item) {
            if ($item === '.' || $item === '..') continue;
            $s = $src . '/' . $item;
            $d = $dest . '/' . $item;
            if (is_dir($s)) {
                if (!self::copyDir($s, $d)) return false;
            } else {
                if (!copy($s, $d)) return false;
            }
        }
        return true;
    }

    /**
     * Recursively delete a directory.
     */
    private static function removeDir(string $dir): void {
        foreach (scandir($dir) as $item) {
            if ($item === '.' || $item === '..') continue;
            $path = $dir . '/' . $item;
            is_dir($path) ? self::removeDir($path) : unlink($path);
        }
        rmdir($dir);
    }
}
