<?php
/**
 * 蜜蜂AI建站 - 轻量插件引擎
 * 职责：Hook 注册/触发 + 插件发现/加载 + 插件配置管理
 * 零侵入设计：仅在 bootstrap.php 末尾 require 本文件即可启用
 */

// ==================== Hook 事件总线 ====================

$GLOBALS['_beeai_hooks'] = array();

/**
 * 注册一个 Hook 监听器
 * @param string   $hookName  钩子名称
 * @param callable $callback  回调函数
 * @param int      $priority  优先级（越小越先执行，默认10）
 */
function hook_add($hookName, $callback, $priority = 10)
{
    $GLOBALS['_beeai_hooks'][$hookName][] = array(
        'callback' => $callback,
        'priority' => (int)$priority
    );
}

/**
 * 移除指定 Hook 上的所有监听器
 * @param string $hookName 钩子名称
 */
function hook_remove_all($hookName)
{
    unset($GLOBALS['_beeai_hooks'][$hookName]);
}

/**
 * 检查指定 Hook 是否有监听器
 * @param string $hookName 钩子名称
 * @return bool
 */
function hook_has($hookName)
{
    return !empty($GLOBALS['_beeai_hooks'][$hookName]);
}

/**
 * 触发一个 Action Hook（无返回值，执行副作用）
 * @param string $hookName  钩子名称
 * @param mixed  ...$args   传递给回调的参数
 */
function hook_do($hookName)
{
    if (empty($GLOBALS['_beeai_hooks'][$hookName])) {
        return;
    }
    $hooks = $GLOBALS['_beeai_hooks'][$hookName];
    usort($hooks, function ($a, $b) { return $a['priority'] - $b['priority']; });
    $args = array_slice(func_get_args(), 1);
    foreach ($hooks as $hook) {
        try {
            call_user_func_array($hook['callback'], $args);
        } catch (Exception $e) {
            error_log('[BeeAI Hook] Error in "' . $hookName . '": ' . $e->getMessage());
        }
    }
}

/**
 * 触发一个 Filter Hook（链式传递并返回修改后的值）
 * @param string $hookName  钩子名称
 * @param mixed  $value     初始值
 * @param mixed  ...$args   额外参数
 * @return mixed            经过所有回调处理后的值
 */
function hook_filter($hookName, $value)
{
    if (empty($GLOBALS['_beeai_hooks'][$hookName])) {
        return $value;
    }
    $hooks = $GLOBALS['_beeai_hooks'][$hookName];
    usort($hooks, function ($a, $b) { return $a['priority'] - $b['priority']; });
    $args = array_slice(func_get_args(), 1);
    foreach ($hooks as $hook) {
        try {
            $args[0] = call_user_func_array($hook['callback'], $args);
        } catch (Exception $e) {
            error_log('[BeeAI Hook] Filter error in "' . $hookName . '": ' . $e->getMessage());
        }
    }
    return $args[0];
}

// ==================== 插件发现与加载 ====================

define('PLUGINS_PATH', BASE_PATH . '/plugins');

/**
 * 获取已启用的插件列表（从 runtime config 读取）
 * @return array 插件 slug 数组
 */
function plugin_enabled_list()
{
    $config = app_runtime_config();
    return isset($config['plugins_enabled']) && is_array($config['plugins_enabled'])
        ? $config['plugins_enabled']
        : array();
}

/**
 * 判断插件是否已启用
 * @param string $slug 插件目录名
 * @return bool
 */
function plugin_is_enabled($slug)
{
    return in_array($slug, plugin_enabled_list(), true);
}

/**
 * 扫描 plugins/ 目录，返回所有已安装插件的元信息
 * @return array slug => meta
 */
function plugin_scan_all()
{
    $plugins = array();
    $dir = PLUGINS_PATH;
    if (!is_dir($dir)) { return $plugins; }

    foreach (scandir($dir) as $folder) {
        if ($folder === '.' || $folder === '..' || $folder === '.htaccess') { continue; }
        $pluginDir = $dir . '/' . $folder;
        if (!is_dir($pluginDir)) { continue; }
        $entryFile = $pluginDir . '/plugin.php';
        if (!is_file($entryFile)) { continue; }

        $meta = plugin_read_meta($entryFile, $folder);
        $meta['enabled'] = plugin_is_enabled($folder);
        $plugins[$folder] = $meta;
    }
    return $plugins;
}

/**
 * 从插件入口文件头部 docblock 读取元信息
 * 支持标签：@Name @Description @Version @Author @Requires @Icon
 */
function plugin_read_meta($file, $slug)
{
    $defaults = array(
        'slug'        => $slug,
        'name'        => $slug,
        'version'     => '1.0.0',
        'description' => '',
        'author'      => '',
        'requires'    => '',
        'icon'        => '🔌',
        'enabled'     => false
    );

    $content = @file_get_contents($file, false, null, 0, 2048);
    if (!is_string($content)) {
        return $defaults;
    }

    if (preg_match('#/\*\*(.*?)\*/#s', $content, $m)) {
        $docblock = $m[1];
        $tags = array('name', 'description', 'version', 'author', 'requires', 'icon');
        foreach ($tags as $tag) {
            if (preg_match('/@' . preg_quote($tag, '/') . '\s+(.+)/i', $docblock, $km)) {
                $defaults[$tag] = trim($km[1]);
            }
        }
    }

    return $defaults;
}

/**
 * 加载所有已启用的插件
 * 在 bootstrap.php 末尾调用
 */
function plugin_load_all()
{
    $dir = PLUGINS_PATH;
    if (!is_dir($dir)) { return; }

    $enabled = plugin_enabled_list();
    if (empty($enabled)) { return; }

    foreach ($enabled as $slug) {
        $slug = trim((string)$slug);
        if ($slug === '') { continue; }
        // 安全校验：slug 只允许字母、数字、连字符、下划线
        if (!preg_match('/^[a-zA-Z0-9_-]+$/', $slug)) {
            error_log('[BeeAI Plugin] Invalid slug: "' . $slug . '"');
            continue;
        }
        $entryFile = $dir . '/' . $slug . '/plugin.php';
        if (!is_file($entryFile)) {
            error_log('[BeeAI Plugin] Entry file not found: "' . $entryFile . '"');
            continue;
        }
        try {
            require_once $entryFile;
        } catch (Exception $e) {
            error_log('[BeeAI Plugin] Failed to load "' . $slug . '": ' . $e->getMessage());
        }
    }

    // 所有插件加载完毕后触发 init hook
    hook_do('plugins_loaded');
}

// ==================== 插件配置管理 ====================

/**
 * 获取指定插件的配置
 * @param string $slug  插件目录名
 * @return array
 */
function plugin_get_config($slug)
{
    $config = app_runtime_config();
    $key = 'plugin_config_' . $slug;
    return isset($config[$key]) && is_array($config[$key]) ? $config[$key] : array();
}

/**
 * 保存指定插件的配置（覆盖整个配置数组）
 * @param string $slug   插件目录名
 * @param array  $values 配置数组
 */
function plugin_save_config($slug, $values)
{
    $key = 'plugin_config_' . $slug;
    app_update_runtime_config(array($key => (array)$values));
}

/**
 * 更新指定插件的部分配置（合并）
 * @param string $slug   插件目录名
 * @param array  $values 需要更新的键值对
 */
function plugin_update_config($slug, $values)
{
    $current = plugin_get_config($slug);
    $merged = array_merge($current, (array)$values);
    plugin_save_config($slug, $merged);
}

/**
 * 启用插件
 * @param string $slug 插件目录名
 * @return bool
 */
function plugin_enable($slug)
{
    $slug = trim((string)$slug);
    if ($slug === '') { return false; }

    $entryFile = PLUGINS_PATH . '/' . $slug . '/plugin.php';
    if (!is_file($entryFile)) { return false; }

    $list = plugin_enabled_list();
    if (!in_array($slug, $list, true)) {
        $list[] = $slug;
        app_update_runtime_config(array('plugins_enabled' => array_values($list)));
    }

    // 加载并执行激活钩子
    try {
        require_once $entryFile;
        hook_do('plugin_activate_' . $slug);
    } catch (Exception $e) {
        error_log('[BeeAI Plugin] Activate error for "' . $slug . '": ' . $e->getMessage());
    }

    return true;
}

/**
 * 禁用插件
 * @param string $slug 插件目录名
 * @return bool
 */
function plugin_disable($slug)
{
    $slug = trim((string)$slug);
    if ($slug === '') { return false; }

    $list = plugin_enabled_list();
    $list = array_values(array_filter($list, function ($s) use ($slug) { return $s !== $slug; }));
    app_update_runtime_config(array('plugins_enabled' => $list));

    hook_do('plugin_deactivate_' . $slug);
    return true;
}

/**
 * 删除插件（先禁用再删文件）
 * @param string $slug 插件目录名
 * @return bool
 */
function plugin_delete($slug)
{
    $slug = trim((string)$slug);
    if ($slug === '' || !preg_match('/^[a-zA-Z0-9_-]+$/', $slug)) { return false; }

    // 先禁用
    plugin_disable($slug);

    // 删除配置
    $config = app_runtime_config();
    $configKey = 'plugin_config_' . $slug;
    if (isset($config[$configKey])) {
        unset($config[$configKey]);
        app_write_runtime_config($config);
    }

    // 删除目录
    $dir = PLUGINS_PATH . '/' . $slug;
    if (is_dir($dir)) {
        _plugin_rmdir_recursive($dir);
    }

    return true;
}

/**
 * 递归删除目录
 */
function _plugin_rmdir_recursive($dir)
{
    if (!is_dir($dir)) { return; }
    $items = scandir($dir);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') { continue; }
        $path = $dir . '/' . $item;
        if (is_dir($path)) {
            _plugin_rmdir_recursive($path);
        } else {
            @unlink($path);
        }
    }
    @rmdir($dir);
}

/**
 * 获取插件的 URL 路径前缀
 * @param string $slug 插件目录名
 * @return string
 */
function plugin_url($slug)
{
    return '/plugins/' . $slug;
}

/**
 * 获取插件的文件系统路径
 * @param string $slug 插件目录名
 * @return string
 */
function plugin_path($slug)
{
    return PLUGINS_PATH . '/' . $slug;
}

// ====================================================================
// 云端插件市场 (Plugin Cloud Market)
// ====================================================================

if (!defined('PLUGIN_CLOUD_BASE')) {
    define('PLUGIN_CLOUD_BASE', 'https://cloud.beeai.chat');
}

/**
 * 从云端获取插件市场列表
 * @return array 插件元信息数组
 * @throws Exception
 */
function fetch_cloud_plugin_list()
{
    $url = PLUGIN_CLOUD_BASE . '/plugins.json';
    // 复用模板市场的 HTTP 工具函数
    if (!function_exists('cloud_http_get')) {
        throw new Exception('云端 HTTP 工具未加载，请确认 template-pack.php 已包含。');
    }
    $json = cloud_http_get($url, 10);
    if ($json === false || $json === '') {
        throw new Exception('无法连接云端服务，请检查网络。');
    }
    $data = json_decode($json, true);
    if (!is_array($data) || !isset($data['plugins']) || !is_array($data['plugins'])) {
        throw new Exception('云端返回数据格式异常。');
    }
    return $data['plugins'];
}

/**
 * 从云端下载并安装/更新插件
 * @param string $slug
 * @return array  ['slug'=>..., 'name'=>..., 'version'=>..., 'files_count'=>..., 'updated'=>bool]
 * @throws Exception
 */
function install_plugin_from_cloud($slug)
{
    $slug = trim((string)$slug);
    if ($slug === '' || !preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $slug)) {
        throw new Exception('插件标识不合法。');
    }

    if (function_exists('require_license_guard')) {
        require_license_guard('下载云端插件');
    }

    // 取云端列表，定位目标插件
    $plugins = fetch_cloud_plugin_list();
    $target = null;
    foreach ($plugins as $p) {
        if (isset($p['slug']) && $p['slug'] === $slug) {
            $target = $p;
            break;
        }
    }
    if (!$target) {
        throw new Exception('云端未找到插件「' . $slug . '」。');
    }

    $downloadUrl = isset($target['download_url']) ? trim((string)$target['download_url']) : '';
    if ($downloadUrl === '') {
        $downloadUrl = PLUGIN_CLOUD_BASE . '/plugins/' . urlencode($slug) . '.zip';
    }

    // 下载 ZIP
    $zipContent = cloud_http_get($downloadUrl, 60);
    if ($zipContent === false || strlen($zipContent) < 100) {
        throw new Exception('下载插件包失败，请稍后重试。');
    }

    $tmpFile = tempnam(sys_get_temp_dir(), 'plg_') . '.zip';
    file_put_contents($tmpFile, $zipContent);

    try {
        $result = _install_plugin_zip_from_path($tmpFile, $slug);
    } finally {
        @unlink($tmpFile);
    }

    // 用云端版本号覆盖本地 plugin.php 内的 @Version（仅当文件可写）
    $cloudVersion = isset($target['version']) ? trim((string)$target['version']) : '';
    if ($cloudVersion !== '') {
        $entry = PLUGINS_PATH . '/' . $result['slug'] . '/plugin.php';
        if (is_file($entry) && is_writable($entry)) {
            $content = file_get_contents($entry);
            if ($content !== false) {
                $newContent = preg_replace('/(@Version\s+)\S+/i', '${1}' . $cloudVersion, $content, 1);
                if ($newContent !== null && $newContent !== $content) {
                    file_put_contents($entry, $newContent);
                }
            }
        }
    }

    $result['name'] = isset($target['name']) ? $target['name'] : $result['slug'];
    $result['version'] = $cloudVersion !== '' ? $cloudVersion : (isset($result['version']) ? $result['version'] : '');
    return $result;
}

/**
 * 从本地上传的 ZIP 文件安装插件
 * @param array $file $_FILES['plugin_zip'] 上传文件数组
 * @return array ['slug'=>..., 'name'=>..., 'version'=>..., 'files_count'=>..., 'updated'=>bool]
 * @throws Exception
 */
function install_plugin_from_upload($file)
{
    if (!isset($file['tmp_name']) || !isset($file['error'])) {
        throw new Exception('上传文件参数无效。');
    }
    if ((int)$file['error'] !== UPLOAD_ERR_OK) {
        $errMap = array(
            UPLOAD_ERR_INI_SIZE   => '文件超过 php.ini 限制',
            UPLOAD_ERR_FORM_SIZE  => '文件超过表单限制',
            UPLOAD_ERR_PARTIAL    => '文件上传不完整',
            UPLOAD_ERR_NO_FILE    => '未选择文件',
            UPLOAD_ERR_NO_TMP_DIR => '缺少临时目录',
            UPLOAD_ERR_CANT_WRITE => '写入临时目录失败',
        );
        $msg = isset($errMap[(int)$file['error']]) ? $errMap[(int)$file['error']] : '上传错误码 ' . $file['error'];
        throw new Exception('上传失败：' . $msg);
    }

    $tmpName = $file['tmp_name'];
    if (!is_uploaded_file($tmpName)) {
        throw new Exception('非法上传文件。');
    }

    $originalName = isset($file['name']) ? basename($file['name']) : '';
    $ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
    if ($ext !== 'zip') {
        throw new Exception('仅支持 .zip 格式的插件包。');
    }

    if (!class_exists('ZipArchive')) {
        throw new Exception('服务器未安装 PHP zip 扩展，无法解压。');
    }

    // 先打开 ZIP 确定 slug
    $zip = new ZipArchive();
    if ($zip->open($tmpName) !== true) {
        throw new Exception('无法打开 ZIP 文件，请确认文件完整。');
    }

    // 在 ZIP 中查找 plugin.php 以确定插件 slug
    $foundSlug = '';
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $entryName = $zip->getNameIndex($i);
        if (basename($entryName) === 'plugin.php') {
            $dir = dirname($entryName);
            if ($dir !== '.' && $dir !== '') {
                // 取第一级目录名作为 slug
                $parts = explode('/', $dir);
                $foundSlug = $parts[0];
            } else {
                // plugin.php 在根目录，用 zip 文件名（去扩展名）作为 slug
                $foundSlug = pathinfo($originalName, PATHINFO_FILENAME);
            }
            break;
        }
    }
    $zip->close();

    if ($foundSlug === '') {
        throw new Exception('ZIP 中未找到 plugin.php 入口文件。');
    }

    // 清理 slug
    $foundSlug = preg_replace('/[^a-zA-Z0-9_-]/', '', $foundSlug);
    if ($foundSlug === '') {
        throw new Exception('无法从 ZIP 中提取有效的插件标识。');
    }

    $result = _install_plugin_zip_from_path($tmpName, $foundSlug);

    // 从安装后的 plugin.php 读取名称
    $entryFile = PLUGINS_PATH . '/' . $result['slug'] . '/plugin.php';
    if (is_file($entryFile)) {
        $meta = plugin_read_meta($entryFile, $result['slug']);
        $result['name'] = $meta['name'];
        if (isset($meta['version']) && $meta['version'] !== '1.0.0') {
            $result['version'] = $meta['version'];
        }
    }

    return $result;
}

/**
 * 从指定路径的 ZIP 解压并安装到 plugins/ 目录
 * @param string $zipPath
 * @param string $expectedSlug 期望的插件目录名（来自云端 manifest）
 * @return array
 * @throws Exception
 */
function _install_plugin_zip_from_path($zipPath, $expectedSlug)
{
    if (!class_exists('ZipArchive')) {
        throw new Exception('服务器未安装 PHP zip 扩展，无法解压。');
    }

    $zip = new ZipArchive();
    $openResult = $zip->open($zipPath);
    if ($openResult !== true) {
        throw new Exception('无法打开 ZIP 文件（错误码 ' . $openResult . '）。');
    }

    // 查找 plugin.php 入口
    $entryIdx = -1;
    $prefix = '';
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $entryName = $zip->getNameIndex($i);
        if (basename($entryName) === 'plugin.php') {
            $entryIdx = $i;
            $dir = dirname($entryName);
            $prefix = ($dir === '.' || $dir === '') ? '' : $dir . '/';
            break;
        }
    }
    if ($entryIdx === -1) {
        $zip->close();
        throw new Exception('ZIP 中未找到 plugin.php 入口文件。');
    }

    $slug = $expectedSlug;
    if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/', $slug)) {
        $zip->close();
        throw new Exception('插件 slug 不合法：' . $slug);
    }

    $isUpdate = is_dir(PLUGINS_PATH . '/' . $slug);
    $targetDir = PLUGINS_PATH . '/' . $slug;

    // 覆盖更新：先删除旧目录
    if ($isUpdate) {
        _plugin_rmdir_recursive($targetDir);
    }
    if (!@mkdir($targetDir, 0777, true)) {
        $zip->close();
        throw new Exception('创建插件目录失败：' . $targetDir);
    }

    $allowedExt = array('php', 'html', 'htm', 'css', 'js', 'json', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'txt', 'md', 'sql', 'woff', 'woff2', 'ttf', 'eot', 'otf');
    $extracted = 0;
    $version = '1.0.0';

    for ($i = 0; $i < $zip->numFiles; $i++) {
        $entryName = $zip->getNameIndex($i);
        if ($prefix !== '' && strpos($entryName, $prefix) !== 0) { continue; }
        $rel = $prefix !== '' ? substr($entryName, strlen($prefix)) : $entryName;
        if ($rel === '' || $rel === false) { continue; }
        if (strpos($rel, '..') !== false) { continue; }

        // 目录
        if (substr($entryName, -1) === '/') {
            $sub = $targetDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, rtrim($rel, '/'));
            if (!is_dir($sub)) { @mkdir($sub, 0777, true); }
            continue;
        }

        $ext = strtolower(pathinfo($rel, PATHINFO_EXTENSION));
        if ($ext !== '' && !in_array($ext, $allowedExt)) { continue; }

        $content = $zip->getFromIndex($i);
        if ($content === false) { continue; }

        $destFile = $targetDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $rel);
        $destDir = dirname($destFile);
        if (!is_dir($destDir)) { @mkdir($destDir, 0777, true); }
        if (file_put_contents($destFile, $content) !== false) {
            $extracted++;
            // 抓取 plugin.php 的版本号
            if (basename($rel) === 'plugin.php') {
                if (preg_match('/@Version\s+(\S+)/i', $content, $vm)) {
                    $version = trim($vm[1]);
                }
            }
        }
    }
    $zip->close();

    if ($extracted === 0) {
        _plugin_rmdir_recursive($targetDir);
        throw new Exception('插件包内无可解压文件。');
    }

    if (!is_file($targetDir . '/plugin.php')) {
        _plugin_rmdir_recursive($targetDir);
        throw new Exception('解压后未发现 plugin.php，安装失败。');
    }

    return array(
        'slug'        => $slug,
        'name'        => $slug,
        'version'     => $version,
        'files_count' => $extracted,
        'updated'     => $isUpdate
    );
}
