"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const adm_zip_1 = __importDefault(require("adm-zip"));
const plugin_manager_1 = require("../plugin-manager");
const authz_1 = require("../middleware/authz");
const plugin_session_1 = require("../plugin-session");
const router = (0, express_1.Router)();
/** Block SSRF: only allow HTTPS downloads from trusted hosts. */
function isAllowedDownloadUrl(url) {
    try {
        const parsed = new URL(url);
        if (parsed.protocol !== 'https:')
            return false;
        const allowedHosts = ['raw.githubusercontent.com', 'github.com', 'objects.githubusercontent.com'];
        return allowedHosts.some(h => parsed.hostname === h || parsed.hostname.endsWith('.' + h));
    }
    catch {
        return false;
    }
}
// ── List installed plugins ───────────────────────────────────────────────────
router.get('/', (_req, res) => {
    const plugins = plugin_manager_1.pluginManager.getAll();
    res.json(plugins.map((p) => ({
        id: p.manifest.id,
        name: p.manifest.name,
        version: p.manifest.version,
        author: p.manifest.author,
        description: p.manifest.description,
        icon: p.manifest.icon,
        type: p.manifest.type,
        float: p.manifest.float,
        permissions: p.manifest.permissions,
        hasBackend: !!p.manifest.backend,
        enabled: p.registry.enabled,
        installedAt: p.registry.installedAt,
        userConfig: p.registry.userConfig,
    })));
});
// ── Install plugin from Hub (download zip, extract, install) ─────────────────
router.post('/install', authz_1.requireAdmin, async (req, res) => {
    const { downloadUrl } = req.body;
    if (!downloadUrl) {
        return res.status(400).json({ error: 'downloadUrl required' });
    }
    if (!isAllowedDownloadUrl(downloadUrl)) {
        return res.status(400).json({ error: 'Download URL must be HTTPS from github.com' });
    }
    const tmpDir = path.join(os.tmpdir(), `ccweb-plugin-${Date.now()}`);
    try {
        // Download zip
        const response = await fetch(downloadUrl, { redirect: 'follow' });
        if (!response.ok)
            throw new Error(`Download failed: ${response.status}`);
        const buffer = Buffer.from(await response.arrayBuffer());
        // Extract zip using adm-zip (pure JS, no external dependency)
        const extractDir = path.join(tmpDir, 'extracted');
        fs.mkdirSync(extractDir, { recursive: true });
        const zip = new adm_zip_1.default(buffer);
        zip.extractAllTo(extractDir, true);
        // Find manifest.json (may be in a subdirectory)
        const manifestDir = findManifestDir(extractDir);
        if (!manifestDir) {
            return res.status(400).json({ error: 'No manifest.json found in archive' });
        }
        const manifest = plugin_manager_1.pluginManager.install(manifestDir);
        res.json({ success: true, plugin: { id: manifest.id, name: manifest.name, version: manifest.version } });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : 'Install failed';
        res.status(500).json({ error: message });
    }
    finally {
        // Cleanup tmp
        try {
            fs.rmSync(tmpDir, { recursive: true, force: true });
        }
        catch { /* */ }
    }
});
// ── Uninstall ────────────────────────────────────────────────────────────────
router.delete('/:id', authz_1.requireAdmin, (req, res) => {
    const { id } = req.params;
    const plugin = plugin_manager_1.pluginManager.get(id);
    if (!plugin)
        return res.status(404).json({ error: 'Plugin not found' });
    plugin_manager_1.pluginManager.uninstall(id);
    res.json({ success: true });
});
// ── Update (re-install from Hub) ─────────────────────────────────────────────
router.post('/:id/update', authz_1.requireAdmin, async (req, res) => {
    const { downloadUrl } = req.body;
    if (!downloadUrl)
        return res.status(400).json({ error: 'downloadUrl required' });
    if (!isAllowedDownloadUrl(downloadUrl)) {
        return res.status(400).json({ error: 'Download URL must be HTTPS from github.com' });
    }
    const tmpDir = path.join(os.tmpdir(), `ccweb-plugin-${Date.now()}`);
    try {
        const response = await fetch(downloadUrl, { redirect: 'follow' });
        if (!response.ok)
            throw new Error(`Download failed: ${response.status}`);
        const buffer = Buffer.from(await response.arrayBuffer());
        const extractDir = path.join(tmpDir, 'extracted');
        fs.mkdirSync(extractDir, { recursive: true });
        const zip = new adm_zip_1.default(buffer);
        zip.extractAllTo(extractDir, true);
        const manifestDir = findManifestDir(extractDir);
        if (!manifestDir)
            return res.status(400).json({ error: 'No manifest.json found' });
        const manifest = plugin_manager_1.pluginManager.install(manifestDir);
        res.json({ success: true, plugin: { id: manifest.id, name: manifest.name, version: manifest.version } });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : 'Update failed';
        res.status(500).json({ error: message });
    }
    finally {
        try {
            fs.rmSync(tmpDir, { recursive: true, force: true });
        }
        catch { /* */ }
    }
});
// ── User config ──────────────────────────────────────────────────────────────
router.put('/:id/config', authz_1.requireAdmin, (req, res) => {
    const { id } = req.params;
    try {
        const entry = plugin_manager_1.pluginManager.updateUserConfig(id, req.body);
        if (!entry)
            return res.status(404).json({ error: 'Plugin not found' });
        res.json(entry);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : 'Invalid config';
        res.status(400).json({ error: message });
    }
});
// ── Enable / disable ─────────────────────────────────────────────────────────
router.put('/:id/enabled', authz_1.requireAdmin, (req, res) => {
    const { id } = req.params;
    const { enabled } = req.body;
    if (typeof enabled !== 'boolean')
        return res.status(400).json({ error: 'enabled (boolean) required' });
    plugin_manager_1.pluginManager.setEnabled(id, enabled);
    res.json({ success: true });
});
// ── Plugin session token (used by /api/plugin-bridge/* authorization) ───────
router.post('/:id/session', (req, res) => {
    const authReq = req;
    const username = authReq.user?.username;
    if (!username)
        return res.status(401).json({ error: 'Unauthorized' });
    try {
        const token = (0, plugin_session_1.issuePluginSessionToken)(req.params.id, username);
        res.json({ token, expiresIn: plugin_session_1.PLUGIN_SESSION_TTL_SECONDS });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : 'Failed to issue token';
        res.status(404).json({ error: message });
    }
});
// Plugin private data is served by /api/plugin-bridge/storage/:pluginId —
// the duplicate routes that used to live here were redundant and unauthenticated.
// ── Helper ───────────────────────────────────────────────────────────────────
function findManifestDir(dir) {
    if (fs.existsSync(path.join(dir, 'manifest.json')))
        return dir;
    // Check one level deep (zip might contain a wrapper folder)
    for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
        if (entry.isDirectory()) {
            const sub = path.join(dir, entry.name);
            if (fs.existsSync(path.join(sub, 'manifest.json')))
                return sub;
        }
    }
    return null;
}
exports.default = router;
//# sourceMappingURL=plugins.js.map