import { createSandboxRequire } from './nodePolyfills';
import type { PluginFullInfo, PluginSearchResult, UrlResponse, PluginModule } from '../types/plugin';
import { useLogStore } from '../stores/log';

// ===== 官方音源插件注册表 =====
export interface BuiltinPluginDef {
    id: string;
    platform: string;
    name: string;
    fileName: string;
    description?: string;
}

const BUILTIN_PLUGINS: BuiltinPluginDef[] = [
    { id: 'wy', platform: 'wy', name: '网易云音乐', fileName: 'zq_wy_v3.js', description: '官方网易云音源插件 v3' },
    { id: 'tx', platform: 'tx', name: 'QQ 音乐', fileName: 'zq_tx_v3-fix1.js', description: '官方 QQ 音乐音源插件 v3' },
    { id: 'kw', platform: 'kw', name: '酷我音乐', fileName: 'zq_kw_v3-fix1.js', description: '官方酷我音源插件 v3' },
    { id: 'kg', platform: 'kg', name: '酷狗音乐', fileName: 'zq_kg.js', description: '官方酷狗音源插件' },
    { id: 'mg', platform: 'mg', name: '咪咕音乐', fileName: 'zq_mg_v3.js', description: '官方咪咕音源插件 v3' },
];

// 支持的插件源 URL 前缀 - 按优先级排序
function getPluginSourcePrefixes(): string[] {
    // 优先从 localStorage 读取自定义源（用户可以配置）
    try {
        const custom = JSON.parse(localStorage.getItem('qz-plugin-sources') || '[]');
        if (Array.isArray(custom) && custom.length > 0) {
            return custom.map((s: string) => s.endsWith('/') ? s : s + '/');
        }
    } catch { /* noop */ }
    return [
        '/plugins/',         // 本地构建产物 /plugins/ 目录（通过 install.sh 下载）
        'https://cdn.jsdelivr.net/gh/your-repo/plugins@latest/',  // CDN 回退
    ];
}

interface LoadedPlugin {
    id: string;
    name: string;
    description: string;
    quality: any[];
    version: string;
    module: PluginModule;
    source: 'built-in' | 'user';
}

/**
 * PluginManager 负责：
 *  1) 加载并执行 webpack bundle / CommonJS 风格插件代码
 *  2) 维护已加载插件清单、激活插件
 *  3) 作为 musicSearch / getUrl / getLyric 的统一入口
 *  4) localStorage 持久化用户插件
 */
export class PluginManager {
    private plugins: Map<string, LoadedPlugin> = new Map();
    private activePluginId: string = '';
    private userPluginCodes: Map<string, string> = new Map();
    private loadingPromises: Map<string, Promise<boolean>> = new Map();

    constructor() {
        // 从 localStorage 恢复用户插件代码
        try {
            const saved = JSON.parse(localStorage.getItem('qz-user-plugins') || '{}');
            for (const [id, code] of Object.entries(saved)) {
                this.userPluginCodes.set(id, code as string);
            }
        } catch { /* noop */ }

        // 恢复激活插件
        const savedActive = sessionStorage.getItem('qz-active-plugin');
        if (savedActive) this.activePluginId = savedActive;
    }

    // ========== 查询类 ==========
    getAll(): PluginFullInfo[] {
        const result: PluginFullInfo[] = [];
        for (const [id, lp] of this.plugins.entries()) {
            result.push({
                id,
                name: lp.name || id,
                description: lp.description || '',
                quality: lp.quality,
                version: lp.version,
                source: lp.source,
            } as any);
        }
        return result;
    }

    getAllBuiltinDefs(): BuiltinPluginDef[] {
        return BUILTIN_PLUGINS.slice();
    }

    has(id: string): boolean {
        return this.plugins.has(id);
    }

    get(id: string): PluginModule | undefined {
        return this.plugins.get(id)?.module;
    }

    getActivePluginId(): string {
        return this.activePluginId;
    }

    getActivePlugin(): PluginModule | undefined {
        return this.plugins.get(this.activePluginId)?.module;
    }

    setActivePlugin(id: string): boolean {
        if (this.plugins.has(id)) {
            this.activePluginId = id;
            try { sessionStorage.setItem('qz-active-plugin', id); } catch { /* noop */ }
            return true;
        }
        return false;
    }

    getQualityList(id: string): { id: string; name: string; ui?: string }[] {
        const mod = this.plugins.get(id);
        if (mod && mod.quality && mod.quality.length > 0) return mod.quality;
        return [
            { id: 'standard', name: '标准', ui: '标' },
            { id: 'exhigh', name: '极高', ui: 'HQ' },
            { id: 'lossless', name: '无损', ui: 'SQ' },
            { id: 'hires', name: 'Hi-Res', ui: 'HR' },
        ];
    }

    // ========== 插件环境变量管理 ==========
    getEnvDefs(id: string): { key: string; name: string; description?: string }[] {
        const lp = this.plugins.get(id);
        if (lp && lp.module && lp.module.pluginInfo && lp.module.pluginInfo.env) {
            return lp.module.pluginInfo.env as any[];
        }
        return [];
    }

    setEnvVar(key: string, value: string): void {
        try {
            const envData = JSON.parse(localStorage.getItem('qz-plugin-env') || '{}');
            envData[key] = value;
            localStorage.setItem('qz-plugin-env', JSON.stringify(envData));
        } catch { /* noop */ }
    }

    getEnvVar(key: string): string {
        try {
            const envData = JSON.parse(localStorage.getItem('qz-plugin-env') || '{}');
            return envData[key] || '';
        } catch { return ''; }
    }

    getAllEnvVars(): Record<string, string> {
        try { return JSON.parse(localStorage.getItem('qz-plugin-env') || '{}'); } catch { return {}; }
    }

    // ========== 插件代码加载 ==========
    /**
     * 从 JS 代码字符串加载插件。
     * 兼容：
     *   1) module.exports = { pluginInfo, musicSearch, getUrl, getLyric } （CommonJS）
     *   2) webpack 打包的 UMD/IIFE bundle（会通过赋值到 this / globalThis 或直接调用安装函数）
     *   3) 通过 window.__plugin_install__ 安装
     */
    loadFromCode(code: string, preferredId?: string, source: 'built-in' | 'user' = 'built-in')
        : { success: boolean; pluginId: string; module?: PluginModule; error?: string } {
        if (!code || typeof code !== 'string') {
            return { success: false, pluginId: '', error: '代码为空' };
        }

        let module: PluginModule | null = null;
        try {
            module = this._executePluginCode(code);
        } catch (err: any) {
            const msg = '执行插件代码时出现异常: ' + (err?.message || String(err));
            try { useLogStore().error('plugin', msg, err); } catch { /* ignore */ }
            return {
                success: false,
                pluginId: '',
                error: msg,
            };
        }

        if (!module || typeof module !== 'object') {
            const msg = '插件未导出对象';
            try { useLogStore().warn('plugin', msg); } catch { /* ignore */ }
            return { success: false, pluginId: '', error: msg };
        }
        if (!module.pluginInfo || !module.pluginInfo.info) {
            const msg = '插件缺少 pluginInfo.info 字段';
            try { useLogStore().warn('plugin', msg); } catch { /* ignore */ }
            return { success: false, pluginId: '', error: msg };
        }

        const id = preferredId || module.pluginInfo.info.id || 'anonymous';
        const name = module.pluginInfo.info.name || id;
        const version = module.pluginInfo.info.version || '1.0';
        const description = module.pluginInfo.info.description || '';
        const quality = (module.pluginInfo.quality as any[]) || [];

        this.plugins.set(id, {
            id, name, description, quality, version, module, source,
        });

        if (!this.activePluginId) this.activePluginId = id;

        // 用户插件代码持久化
        if (source === 'user') {
            this.userPluginCodes.set(id, code);
            this._persistUserPlugins();
        }

        try { useLogStore().info('plugin', `已加载音源插件: ${name} (${id}) v${version}`); } catch { /* ignore */ }

        return { success: true, pluginId: id, module };
    }

    /**
     * 通过 registerModule 注册一个直接构造好的 PluginModule（用于 demo / 默认插件）
     */
    registerModule(mod: PluginModule, source: 'built-in' | 'user' = 'built-in', preferredId?: string): boolean {
        if (!mod || !mod.pluginInfo || !mod.pluginInfo.info) return false;
        const id = preferredId || mod.pluginInfo.info.id || 'demo';
        this.plugins.set(id, {
            id,
            name: mod.pluginInfo.info.name || id,
            description: mod.pluginInfo.info.description || '',
            quality: (mod.pluginInfo.quality as any[]) || [],
            version: mod.pluginInfo.info.version || '1.0',
            module: mod,
            source,
        });
        if (!this.activePluginId) this.activePluginId = id;
        return true;
    }

    /** 卸载并删除用户插件 */
    removeUserPlugin(id: string): boolean {
        const lp = this.plugins.get(id);
        if (lp && lp.source === 'user') {
            this.plugins.delete(id);
        }
        this.userPluginCodes.delete(id);
        this._persistUserPlugins();
        if (this.activePluginId === id) this._pickFallbackActive();
        return true;
    }

    /** 按 id 卸载任何插件（含内置） */
    unregister(id: string): boolean {
        const existed = this.plugins.delete(id);
        this.userPluginCodes.delete(id);
        this._persistUserPlugins();
        if (this.activePluginId === id) this._pickFallbackActive();
        return existed;
    }

    /** 把用户上传的 JS 代码保存一份 */
    saveUserPlugin(code: string, id?: string): string | null {
        try {
            const saved = JSON.parse(localStorage.getItem('qz-user-plugins') || '{}');
            const realId = id || ('user_' + Date.now());
            saved[realId] = code;
            localStorage.setItem('qz-user-plugins', JSON.stringify(saved));
            this.userPluginCodes.set(realId, code);
            return realId;
        } catch {
            return null;
        }
    }

    /** 从 localStorage 重新加载用户插件，返回成功加载数 */
    loadUserPlugins(): number {
        let loaded = 0;
        for (const [id, code] of this.userPluginCodes.entries()) {
            try {
                const res = this.loadFromCode(code, id, 'user');
                if (res.success) loaded++;
            } catch { /* noop */ }
        }
        return loaded;
    }

    private _persistUserPlugins() {
        try {
            const toSave: Record<string, string> = {};
            this.userPluginCodes.forEach((c, k) => { toSave[k] = c; });
            localStorage.setItem('qz-user-plugins', JSON.stringify(toSave));
        } catch { /* noop */ }
    }

    private _pickFallbackActive() {
        if (this.plugins.size === 0) {
            this.activePluginId = '';
            sessionStorage.removeItem('qz-active-plugin');
            return;
        }
        // 优先 wy，否则取第一个
        if (this.plugins.has('wy')) {
            this.activePluginId = 'wy';
        } else {
            this.activePluginId = this.plugins.keys().next().value || '';
        }
        try { sessionStorage.setItem('qz-active-plugin', this.activePluginId); } catch { /* noop */ }
    }

    // ========== 网络下载加载 ==========
    async loadFromUrl(url: string, preferredId?: string, source: 'built-in' | 'user' = 'built-in'): Promise<boolean> {
        const cacheKey = url;
        if (this.loadingPromises.has(cacheKey)) {
            return this.loadingPromises.get(cacheKey)!;
        }
        const promise = (async (): Promise<boolean> => {
            try {
                const resp = await fetch(url, { cache: 'no-store' });
                if (!resp.ok) return false;
                const code = await resp.text();
                const result = this.loadFromCode(code, preferredId, source);
                return result.success;
            } catch (err) {
                console.warn('[PluginManager] loadFromUrl 失败:', url, err);
                return false;
            }
        })();
        this.loadingPromises.set(cacheKey, promise);
        const result = await promise;
        this.loadingPromises.delete(cacheKey);
        return result;
    }

    async loadBuiltin(def: BuiltinPluginDef): Promise<boolean> {
        if (this.has(def.id)) return true;
        const prefixes = getPluginSourcePrefixes();
        for (const prefix of prefixes) {
            if (await this.loadFromUrl(prefix + def.fileName, def.id, 'built-in')) {
                // 拉成功后用配置里的 name/desc 更新元信息（插件内的 info 可能为英文）
                const lp = this.plugins.get(def.id);
                if (lp) {
                    lp.name = def.name;
                    if (def.description) lp.description = def.description;
                }
                return true;
            }
        }
        return false;
    }

    /** 并行加载所有内置音源插件，返回加载情况 */
    async loadAllBuiltins(): Promise<{ loaded: number; total: number; failed: string[] }> {
        const failed: string[] = [];
        const tasks = BUILTIN_PLUGINS.map(def =>
            this.loadBuiltin(def).then(ok => {
                if (!ok) failed.push(def.id);
                return ok;
            })
        );
        const results = await Promise.allSettled(tasks);
        let loaded = 0;
        for (const r of results) {
            if (r.status === 'fulfilled' && r.value) loaded++;
        }
        if (!this.activePluginId && this.plugins.size > 0) {
            this._pickFallbackActive();
        }
        try {
            useLogStore().info('plugin', `官方音源加载完成: ${loaded}/${BUILTIN_PLUGINS.length} 已加载，激活音源: ${this.activePluginId || '(无)'}`);
            if (failed.length > 0) {
                useLogStore().warn('plugin', `未能加载的音源: ${failed.join(', ')}`);
            }
        } catch { /* ignore */ }
        return { loaded, total: BUILTIN_PLUGINS.length, failed };
    }

    // ========== 插件 API 统一入口 ==========
    async search(query: string, page: number, limit: number, preferredPluginId?: string): Promise<PluginSearchResult> {
        const pid = preferredPluginId || this.activePluginId;
        if (!pid) return { list: [], total: 0, error: '未激活音源插件' };
        const lp = this.plugins.get(pid);
        if (!lp) return { list: [], total: 0, error: '音源插件未加载: ' + pid };
        const mod = lp.module;
        let searchFn: ((q: string, p: number, l: number) => Promise<any>) | undefined;
        if (mod.musicSearch && typeof (mod.musicSearch as any).search === 'function') {
            searchFn = (mod.musicSearch as any).search.bind(mod.musicSearch);
        } else if (typeof mod.musicSearch === 'function') {
            searchFn = mod.musicSearch;
        }
        if (!searchFn) return { list: [], total: 0, error: '插件不支持搜索' };
        try {
            const result = await searchFn(query, page, limit);
            if (!result) return { list: [], total: 0, error: '搜索返回空' };
            const list = Array.isArray(result.list) ? result.list : [];
            return {
                list,
                total: result.total ?? result.songCount ?? list.length,
                songCount: result.songCount,
                allPage: result.allPage,
                source: pid,
                error: undefined,
            };
        } catch (err: any) {
            console.error('[PluginManager] search 失败:', err);
            return { list: [], total: 0, error: err?.message || '搜索失败' };
        }
    }

    async getSongUrl(song: { id?: string; songmid?: string; source?: string }, quality?: string): Promise<UrlResponse> {
        const songId = song.songmid || song.id;
        if (!songId) return { success: false, error: '歌曲缺少 id' };
        const candidateIds: string[] = [];
        if (song.source && this.plugins.has(song.source)) candidateIds.push(song.source);
        if (this.activePluginId && !candidateIds.includes(this.activePluginId)) candidateIds.push(this.activePluginId);
        for (const pid of this.plugins.keys()) if (!candidateIds.includes(pid)) candidateIds.push(pid);

        const errors: string[] = [];
        for (const pid of candidateIds) {
            const lp = this.plugins.get(pid);
            if (!lp || typeof lp.module.getUrl !== 'function') continue;
            try {
                const q = quality || (lp.quality && lp.quality[0]?.id) || 'standard';
                const result = await lp.module.getUrl(songId, q);
                let url: string | undefined;
                if (typeof result === 'string') url = result;
                else if (result && typeof result === 'object') {
                    url = (result as any).url || (result as any).data?.url || (result as any).data;
                    if (typeof url !== 'string') url = undefined;
                }
                if (url && typeof url === 'string' && /^(https?:)?\/\//i.test(url)) {
                    return { success: true, url, pluginId: pid };
                }
                if (url && typeof url === 'string' && url.startsWith('//')) {
                    return { success: true, url: 'https:' + url, pluginId: pid };
                }
            } catch (err: any) {
                console.warn(`[PluginManager] ${pid} getUrl 失败:`, err);
                errors.push(pid + ':' + (err?.message || '失败'));
            }
        }
        return { success: false, error: '所有插件都未能获取播放地址 (' + errors.join('; ') + ')' };
    }

    async getLyric(song: { id?: string; songmid?: string; source?: string }): Promise<{ format: string | null; raw: any; error?: string }> {
        const songId = song.songmid || song.id;
        if (!songId) return { format: null, raw: null, error: '歌曲缺少 id' };
        const candidateIds: string[] = [];
        if (song.source && this.plugins.has(song.source)) candidateIds.push(song.source);
        if (this.activePluginId && !candidateIds.includes(this.activePluginId)) candidateIds.push(this.activePluginId);
        for (const pid of this.plugins.keys()) if (!candidateIds.includes(pid)) candidateIds.push(pid);

        for (const pid of candidateIds) {
            const lp = this.plugins.get(pid);
            if (!lp || typeof lp.module.getLyric !== 'function') continue;
            try {
                const raw = await lp.module.getLyric(songId);
                if (raw === null || raw === undefined) continue;
                const format = detectLyricFormat(raw);
                return { format, raw };
            } catch (err: any) {
                console.warn(`[PluginManager] ${pid} getLyric 失败:`, err);
            }
        }
        return { format: null, raw: null };
    }

    // ========== 内部：执行插件代码 ==========
    private _executePluginCode(code: string): PluginModule | null {
        const moduleObj: any = { exports: {} };
        const requireFn = createSandboxRequire();

        const processObj: any = {
            env: { NODE_ENV: 'production' },
            version: 'v20.0.0',
            platform: 'browser',
            arch: 'x64',
            argv: [],
            title: 'node',
            browser: true,
            cwd: () => '/',
            chdir: () => { /* noop */ },
            on: function () { return this; },
            once: function () { return this; },
            emit: function () { return true; },
            off: function () { return this; },
            addListener: function () { return this; },
            removeListener: function () { return this; },
            removeAllListeners: function () { return this; },
            versions: { node: '20.0.0', v8: '11.3.244.8-node.16' },
            exit: function () { /* noop */ },
            nextTick: (fn: () => void) => setTimeout(fn, 0),
        };

        const textEnc = typeof (globalThis as any).TextEncoder !== 'undefined' ? new (globalThis as any).TextEncoder() : null;
        const textDec = typeof (globalThis as any).TextDecoder !== 'undefined' ? new (globalThis as any).TextDecoder('utf-8') : null;

        const BufferCtor: any = function (arg: any) {
            if (typeof arg === 'string') return textEnc ? textEnc.encode(arg) : new Uint8Array(arg.length);
            if (arg instanceof Uint8Array) return arg;
            if (typeof arg === 'number') return new Uint8Array(arg);
            return new Uint8Array(arg || 0);
        };
        BufferCtor.from = (x: any) => {
            if (x instanceof Uint8Array) return new Uint8Array(x);
            if (typeof x === 'string') return textEnc ? textEnc.encode(x) as Uint8Array : new Uint8Array(0);
            if (Array.isArray(x)) return new Uint8Array(x as any);
            return new Uint8Array(x || 0);
        };
        BufferCtor.alloc = (n: number) => new Uint8Array(n);
        BufferCtor.allocUnsafe = (n: number) => new Uint8Array(n);
        BufferCtor.allocUnsafeSlow = (n: number) => new Uint8Array(n);
        BufferCtor.isBuffer = (x: any) => x instanceof Uint8Array || (x && x.constructor && x.constructor.name === 'Buffer');
        BufferCtor.byteLength = (x: any) => typeof x === 'string'
            ? (textEnc ? textEnc.encode(x).length : x.length)
            : (x?.length || 0);
        BufferCtor.isEncoding = () => true;
        BufferCtor.concat = (arrs: any[]) => {
            const total = arrs.reduce((a: number, b: any) => a + (b?.length || 0), 0);
            const merged = new Uint8Array(total);
            let offset = 0;
            for (const c of arrs) { if (c) { merged.set(c, offset); offset += c.length; } }
            return merged;
        };
        const toOrig = (Uint8Array.prototype as any).toString;
        (BufferCtor as any).prototype = Object.create(Uint8Array.prototype);
        (BufferCtor as any).prototype.toString = function (encoding?: string) {
            try {
                if (encoding === 'base64') {
                    let s = '';
                    const chunk = 0x8000;
                    for (let i = 0; i < this.length; i += chunk) {
                        s += String.fromCharCode.apply(null, Array.from(this.subarray(i, i + chunk)));
                    }
                    return typeof btoa !== 'undefined' ? btoa(s) : s;
                }
                if (encoding === 'hex') {
                    let s = '';
                    for (let i = 0; i < this.length; i++) {
                        s += this[i].toString(16).padStart(2, '0');
                    }
                    return s;
                }
                if (encoding === 'utf8' || encoding === 'utf-8' || !encoding) {
                    return textDec ? textDec.decode(this) : String.fromCharCode.apply(null, Array.from(this));
                }
                return textDec ? textDec.decode(this) : String.fromCharCode.apply(null, Array.from(this));
            } catch {
                return toOrig ? toOrig.call(this) : '';
            }
        };

        const pluginEnv: Record<string, string> = {};
        try {
            const savedEnv = JSON.parse(localStorage.getItem('qz-plugin-env') || '{}');
            Object.assign(pluginEnv, savedEnv);
        } catch { /* noop */ }

        const fakeGlobal: any = {
            process: processObj,
            Buffer: BufferCtor,
            require: requireFn,
            module: moduleObj,
            exports: moduleObj.exports,
            __dirname: '/',
            __filename: '/plugin.js',
            global: globalThis,
            globalThis: globalThis,
            self: globalThis,
            root: globalThis,
            env: pluginEnv,
            console,
            setTimeout: setTimeout as any,
            clearTimeout,
            setInterval,
            clearInterval,
            setImmediate: function (...rest: any[]) { return setTimeout(rest[0], 0); },
            clearImmediate: (id: any) => clearTimeout(id),
            Promise,
            JSON,
            Math,
            Date,
            Array,
            Object,
            String,
            Number,
            Boolean,
            Symbol,
            Error,
            TypeError,
            Uint8Array,
            Uint16Array,
            Uint32Array,
            Int8Array,
            Int16Array,
            Int32Array,
            Float32Array,
            Float64Array,
            TextDecoder: (globalThis as any).TextDecoder,
            TextEncoder: (globalThis as any).TextEncoder,
            fetch: (globalThis as any).fetch,
            Headers: (globalThis as any).Headers,
            Request: (globalThis as any).Request,
            Response: (globalThis as any).Response,
            crypto: (globalThis as any).crypto,
            performance: (globalThis as any).performance,
        };
        // 注意：不再覆盖 window / location / document / navigator
        // 让 with(fakeGlobal) 在查找这些未覆盖变量时自动回落到浏览器真实全局作用域
        // 确保 window.location.href / document.cookie / navigator.userAgent 等正常工作

        // ============================================================
        // 关键修复：临时把 Node 风格全局挂到 window 上
        // webpack/ncc 内部的 __nccwpck_require__ 会通过嵌套 eval/Function
        // 在**全局作用域**查找 require/process/Buffer 等，with(fakeGlobal) 覆盖不到它们
        // 执行后立即恢复，避免污染页面其他代码
        // ============================================================
        const win: any = typeof window !== 'undefined' ? window : globalThis;
        const backup: Record<string, any> = {};
        const globalsToInject: [string, any][] = [
            ['require', requireFn],
            ['process', processObj],
            ['Buffer', BufferCtor],
            ['module', moduleObj],
            ['exports', moduleObj.exports],
            ['__dirname', '/'],
            ['__filename', '/plugin.js'],
            ['global', globalThis],
            ['globalThis', globalThis],
            ['setImmediate', (function (fn: (...rest: any[]) => void, ...rest: any[]) { return setTimeout(fn, 0, ...rest); }) as any],
            ['clearImmediate', (id: any) => clearTimeout(id)],
        ];
        for (const [key, value] of globalsToInject) {
            backup[key] = (win as any)[key];
            try { (win as any)[key] = value; } catch { /* ignore readonly */ }
        }

        try {
            // 用 with(fakeGlobal) 包裹插件代码，同时在顶层参数显式绑定 CommonJS 变量
            const wrapperCode =
                '(function(require,__dirname,__filename,module,exports,process,Buffer){' +
                code +
                '\n;return module.exports;' +
                '})';
            const fn: any = (new Function('return ' + wrapperCode))();
            const args: any[] = [
                requireFn, '/', '/plugin.js', moduleObj, moduleObj.exports, processObj, BufferCtor
            ];
            // 通过 with(fakeGlobal) 调用，让自由变量查找回落到 fakeGlobal
            const withWrapper: any = new Function(
                '__g__', '__fn__', '__args__',
                'with(__g__){ return __fn__.apply(null,__args__); }'
            );
            const result = withWrapper(fakeGlobal, fn, args);
            const mod = (result && typeof result === 'object' && (result as any).exports)
                ? (result as any).exports
                : result;

            if (!mod || (typeof mod === 'object' && Object.keys(mod).length === 0)) {
                const direct = (fakeGlobal.__plugin_install__ as any) || (fakeGlobal as any).plugin;
                if (direct && typeof direct === 'object' && direct.pluginInfo) return direct as PluginModule;
            }

            return mod as PluginModule;
        } finally {
            // 恢复原始全局，避免污染页面其他代码
            for (const key of Object.keys(backup)) {
                try {
                    if (backup[key] === undefined) delete (win as any)[key];
                    else (win as any)[key] = backup[key];
                } catch { /* ignore readonly */ }
            }
        }
    }
}

// ========== 歌词格式识别 ==========
function detectLyricFormat(raw: any): string | null {
    if (raw === null || raw === undefined) return null;
    if (typeof raw === 'string') {
        const s = raw.trim();
        if (!s) return null;
        if (/^<\s*(?:\?xml|tt|TT|lyric\b|Lyric\b|LyricData\b)/i.test(s)) return 'ttml';
        if (/<\s*\d+[:：]\d+/.test(s)) return 'qrc';
        if (/\[\s*\d{1,2}[:：]\d{1,2}(?:[.:：]\d{1,3})?\s*\]/.test(s)) return 'lrc';
        if (s.startsWith('{') || s.startsWith('[')) {
            try {
                const obj = JSON.parse(s);
                if (typeof obj === 'object' && obj !== null) {
                    if ((obj as any).yrc || (obj as any).lrclib || (obj as any).klyric) return 'yrc';
                    if ((obj as any).lrc || (obj as any).lyric) return 'lrc';
                    if ((obj as any).ttml) return 'ttml';
                    if ((obj as any).qrc) return 'qrc';
                    return 'json';
                }
            } catch { /* noop */ }
        }
        return 'text';
    }
    if (typeof raw === 'object') {
        if ((raw as any).yrc || (raw as any).lrclib || (raw as any).klyric) return 'yrc';
        if ((raw as any).lrc) return 'lrc';
        if ((raw as any).ttml) return 'ttml';
        if ((raw as any).qrc) return 'qrc';
        return 'json';
    }
    return null;
}

// 全局单例
export const pluginManager = new PluginManager();
