<?php

declare(strict_types=1);


namespace App\Blog\Support;

use App\Models\PluginRecord;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RuntimeException;

class PluginManager
{
    /** @var array<string, array<int, callable>> */
    private array $actions = [];

    /** @var array<string, array<int, callable>> */
    private array $filters = [];

    /** @var array<string, class-string<\App\Blog\Support\PluginServiceProvider>> */
    private array $providers = [];

    public function path(?string $plugin = null): string
    {
        return config('plugins.path').($plugin ? '/'.$plugin : '');
    }

    public function exists(string $plugin): bool
    {
        return is_dir($this->path($plugin)) && File::exists($this->path($plugin).'/plugin.json');
    }

    public function manifest(string $plugin): array
    {
        $file = $this->path($plugin).'/plugin.json';

        if (! File::exists($file)) {
            return [];
        }

        return json_decode(File::get($file), true) ?? [];
    }

    public function all(): array
    {
        $plugins = [];

        foreach (File::directories(config('plugins.path')) as $dir) {
            $basename = basename($dir);
            $manifest = $this->manifest($basename);
            if (! $manifest) {
                continue;
            }

            [$vendor, $name] = array_pad(explode('.', $basename), 2, $basename);

            $requires = $manifest['requires'] ?? [];
            $dependencies = [];
            $dependencyErrors = [];

            foreach ($requires as $req) {
                $installed = $this->exists($req);
                $enabled = $installed && $this->isEnabled($req);
                $dependencies[] = [
                    'key' => $req,
                    'title' => $this->manifest($req)['title'] ?? $req,
                    'ok' => $enabled,
                    'reason' => $installed ? ($enabled ? '' : '未启用') : '未安装',
                ];

                if (! $enabled) {
                    $dependencyErrors[] = $req;
                }
            }

            $plugins[$basename] = [
                'key' => $basename,
                'vendor' => $vendor,
                'name' => $name,
                'title' => $manifest['title'] ?? $name,
                'version' => $manifest['version'] ?? '0.0.0',
                'description' => $manifest['description'] ?? '',
                'usage' => $manifest['usage'] ?? '',
                'author' => $manifest['author'] ?? '',
                'enabled' => $this->isEnabled($basename),
                'requires' => $requires,
                'dependencies' => $dependencies,
                'dependency_errors' => $dependencyErrors,
            ];
        }

        return $plugins;
    }

    public function isEnabled(string $plugin): bool
    {
        if (\Illuminate\Support\Facades\Schema::hasTable('plugin_records')) {
            [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);

            $record = PluginRecord::query()->where('vendor', $vendor)->where('name', $name)->first();

            if ($record) {
                return (bool) $record->enabled;
            }
        }

        return in_array($plugin, config('plugins.enabled', []), true);
    }

    public function requires(string $plugin): array
    {
        return $this->manifest($plugin)['requires'] ?? [];
    }

    /**
     * 依赖未满足的问题列表（空数组 = 依赖完好）。
     */
    public function dependencyProblems(string $plugin): array
    {
        $problems = [];

        foreach ($this->requires($plugin) as $req) {
            if (! $this->exists($req)) {
                $problems[] = "缺少依赖插件：{$req}（未安装）";
            } elseif (! $this->isEnabled($req)) {
                $problems[] = '依赖插件未启用：'.$req.'（'.$this->manifest($req)['title'].'）';
            }
        }

        return $problems;
    }

    /**
     * 已启用且依赖本插件的插件列表。
     */
    public function dependents(string $plugin): array
    {
        $dependents = [];

        foreach ($this->all() as $key => $p) {
            if ($p['enabled'] && in_array($plugin, $p['requires'] ?? [], true)) {
                $dependents[] = $key;
            }
        }

        return $dependents;
    }

    public function enable(string $plugin): void
    {
        $problems = $this->dependencyProblems($plugin);

        if ($problems) {
            throw new RuntimeException('无法启用 '.$plugin.'：'.implode('；', $problems));
        }

        [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);
        PluginRecord::query()->updateOrCreate(
            ['vendor' => $vendor, 'name' => $name],
            ['enabled' => true]
        );
    }

    public function disable(string $plugin): void
    {
        $dependents = $this->dependents($plugin);

        if ($dependents) {
            throw new RuntimeException('无法停用 '.$plugin.'：以下已启用插件依赖它：'.implode('、', $dependents).'。请先停用这些插件。');
        }

        [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);
        PluginRecord::query()->updateOrCreate(
            ['vendor' => $vendor, 'name' => $name],
            ['enabled' => false]
        );
    }

    public function uninstall(string $plugin): void
    {
        $dependents = $this->dependents($plugin);

        if ($dependents) {
            throw new RuntimeException('无法卸载 '.$plugin.'：以下已启用插件依赖它：'.implode('、', $dependents).'。请先停用这些插件。');
        }

        [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);
        PluginRecord::query()->where('vendor', $vendor)->where('name', $name)->delete();
    }

    /**
     * 解析插件提供者类：plugins/{vendor}.{name}/src/PluginServiceProvider.php
     *
     * @return class-string<\App\Blog\Support\PluginServiceProvider>|null
     */
    public function resolveProvider(string $plugin): ?string
    {
        $class = $this->providers[$plugin] ?? null;

        if ($class) {
            return $class;
        }

        // 类名兜底：先找 src/ServiceProvider.php，再找 src/PluginServiceProvider.php
        $manifest = $this->manifest($plugin);
        $class = $manifest['provider'] ?? null;

        if (! $class) {
            $candidates = ['/src/ServiceProvider.php', '/src/PluginServiceProvider.php'];
            foreach ($candidates as $candidate) {
                if (File::exists($this->path($plugin).$candidate)) {
                    [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin);
                    $class = 'Plugins\\'.Str::studly($vendor).'\\'.Str::studly($name).'\\'.basename($candidate, '.php');
                    break;
                }
            }
        }

        return $this->providers[$plugin] = $class;
    }

    public function registerProvider(string $plugin, string $class): void
    {
        $this->providers[$plugin] = $class;
    }

    /* ---------------- Hooks ---------------- */

    public function addAction(string $hook, callable $callback, int $priority = 10): void
    {
        $this->actions[$hook][$priority][] = $callback;
        ksort($this->actions[$hook]);
    }

    public function addFilter(string $hook, callable $callback, int $priority = 10): void
    {
        $this->filters[$hook][$priority][] = $callback;
        ksort($this->filters[$hook]);
    }

    public function doAction(string $hook, mixed ...$args): void
    {
        foreach ($this->actions[$hook] ?? [] as $callbacks) {
            foreach ($callbacks as $callback) {
                $callback(...$args);
            }
        }
    }

    public function applyFilters(string $hook, mixed $value, mixed ...$args): mixed
    {
        foreach ($this->filters[$hook] ?? [] as $callbacks) {
            foreach ($callbacks as $callback) {
                $value = $callback($value, ...$args);
            }
        }

        return $value;
    }
}
