import { EventBus } from '../core/EventBus.js';

/* ────────────────────────────────────────────────────────────────
   Plugin SDK — Architecture for 3rd party plugins
   
   Sprint L9: Plugin lifecycle, sandboxing, dependencies,
   extension points, templates, and marketplace API.
──────────────────────────────────────────────────────────────── */
/* ══════════════════════════════════════════════════════════════
   Plugin Storage (in-memory with optional persistence)
   ══════════════════════════════════════════════════════════════ */
class InMemoryPluginStorage {
    constructor(pluginId) {
        this.data = new Map();
        this.prefix = `nice2dev.plugin.${pluginId}.`;
    }
    get(key) {
        if (this.data.has(key))
            return this.data.get(key);
        // Try localStorage as fallback
        if (typeof localStorage !== 'undefined') {
            const raw = localStorage.getItem(this.prefix + key);
            if (raw !== null) {
                try {
                    return JSON.parse(raw);
                }
                catch ( /* ignore */_a) { /* ignore */ }
            }
        }
        return undefined;
    }
    set(key, value) {
        this.data.set(key, value);
        if (typeof localStorage !== 'undefined') {
            localStorage.setItem(this.prefix + key, JSON.stringify(value));
        }
    }
    remove(key) {
        this.data.delete(key);
        if (typeof localStorage !== 'undefined') {
            localStorage.removeItem(this.prefix + key);
        }
    }
    clear() {
        this.data.clear();
        if (typeof localStorage !== 'undefined') {
            const toRemove = [];
            for (let i = 0; i < localStorage.length; i++) {
                const k = localStorage.key(i);
                if (k === null || k === void 0 ? void 0 : k.startsWith(this.prefix))
                    toRemove.push(k);
            }
            for (const k of toRemove)
                localStorage.removeItem(k);
        }
    }
    keys() {
        return Array.from(this.data.keys());
    }
}
/* ══════════════════════════════════════════════════════════════
   Plugin Manager
   ══════════════════════════════════════════════════════════════ */
class PluginManager {
    constructor() {
        this.events = new EventBus();
        this.plugins = new Map();
        this.extensions = new Map();
    }
    /* ── Install ────────────────────────────────────────────────── */
    install(definition) {
        const id = definition.manifest.id;
        if (this.plugins.has(id)) {
            throw new Error(`[PluginManager] Plugin "${id}" is already installed.`);
        }
        // Check dependencies
        if (definition.manifest.dependencies) {
            for (const depId of Object.keys(definition.manifest.dependencies)) {
                if (!this.plugins.has(depId)) {
                    throw new Error(`[PluginManager] Plugin "${id}" requires "${depId}" which is not installed.`);
                }
            }
        }
        const instance = {
            definition,
            state: 'installed',
            activatedAt: null,
            error: null,
            extensions: new Map(),
        };
        this.plugins.set(id, instance);
        this.events.emit('plugin:installed', { id, manifest: definition.manifest });
    }
    /* ── Activate ───────────────────────────────────────────────── */
    async activate(id) {
        const instance = this.plugins.get(id);
        if (!instance) {
            throw new Error(`[PluginManager] Plugin "${id}" is not installed.`);
        }
        if (instance.state === 'active')
            return;
        const ctx = this.createContext(instance);
        try {
            await instance.definition.activate(ctx);
            instance.state = 'active';
            instance.activatedAt = Date.now();
            instance.error = null;
            this.events.emit('plugin:activated', { id });
        }
        catch (err) {
            instance.state = 'error';
            instance.error = err instanceof Error ? err.message : String(err);
            this.events.emit('plugin:error', { id, error: instance.error });
            throw err;
        }
    }
    /* ── Deactivate ─────────────────────────────────────────────── */
    async deactivate(id) {
        var _a, _b;
        const instance = this.plugins.get(id);
        if (!instance || instance.state !== 'active')
            return;
        const ctx = this.createContext(instance);
        try {
            await ((_b = (_a = instance.definition).deactivate) === null || _b === void 0 ? void 0 : _b.call(_a, ctx));
        }
        catch (err) {
            if (typeof console !== 'undefined') {
                console.warn(`[PluginManager] Error deactivating "${id}":`, err);
            }
        }
        // Remove all extensions registered by this plugin
        for (const [point, entries] of instance.extensions) {
            const global = this.extensions.get(point);
            if (global) {
                for (const entry of entries) {
                    const idx = global.indexOf(entry);
                    if (idx >= 0)
                        global.splice(idx, 1);
                }
            }
        }
        instance.extensions.clear();
        instance.state = 'inactive';
        instance.activatedAt = null;
        this.events.emit('plugin:deactivated', { id });
    }
    /* ── Uninstall ──────────────────────────────────────────────── */
    async uninstall(id) {
        var _a, _b, _c;
        const instance = this.plugins.get(id);
        if (!instance)
            return;
        // Check if other plugins depend on this
        for (const [otherId, other] of this.plugins) {
            if (otherId === id)
                continue;
            if ((_a = other.definition.manifest.dependencies) === null || _a === void 0 ? void 0 : _a[id]) {
                throw new Error(`[PluginManager] Cannot uninstall "${id}": plugin "${otherId}" depends on it.`);
            }
        }
        if (instance.state === 'active') {
            await this.deactivate(id);
        }
        const ctx = this.createContext(instance);
        try {
            await ((_c = (_b = instance.definition).uninstall) === null || _c === void 0 ? void 0 : _c.call(_b, ctx));
        }
        catch (e) {
            console.warn(`Plugin '${id}' uninstall error:`, e);
        }
        this.plugins.delete(id);
        this.events.emit('plugin:uninstalled', { id });
    }
    /* ── Queries ────────────────────────────────────────────────── */
    getPlugin(id) {
        return this.plugins.get(id);
    }
    get allPlugins() {
        return Array.from(this.plugins.values());
    }
    get activePlugins() {
        return this.allPlugins.filter(p => p.state === 'active');
    }
    getExtensions(point) {
        var _a;
        return ((_a = this.extensions.get(point)) !== null && _a !== void 0 ? _a : []);
    }
    /** Search plugins by keyword. */
    search(query) {
        const q = query.toLowerCase();
        return this.allPlugins.filter(p => {
            var _a;
            const m = p.definition.manifest;
            return m.name.toLowerCase().includes(q) ||
                m.description.toLowerCase().includes(q) ||
                ((_a = m.keywords) === null || _a === void 0 ? void 0 : _a.some(k => k.toLowerCase().includes(q))) ||
                m.id.toLowerCase().includes(q);
        });
    }
    /* ── Context factory ────────────────────────────────────────── */
    createContext(instance) {
        const manifest = instance.definition.manifest;
        const pluginId = manifest.id;
        const storage = new InMemoryPluginStorage(pluginId);
        return {
            manifest,
            events: this.events,
            storage,
            log: (msg) => {
                if (typeof console !== 'undefined')
                    console.log(`[Plugin:${pluginId}] ${msg}`);
            },
            warn: (msg) => {
                if (typeof console !== 'undefined')
                    console.warn(`[Plugin:${pluginId}] ${msg}`);
            },
            registerExtension: (point, entry) => {
                // Global
                let global = this.extensions.get(point);
                if (!global) {
                    global = [];
                    this.extensions.set(point, global);
                }
                global.push(entry);
                // Per-plugin tracking
                let pluginExts = instance.extensions.get(point);
                if (!pluginExts) {
                    pluginExts = [];
                    instance.extensions.set(point, pluginExts);
                }
                pluginExts.push(entry);
                this.events.emit('extension:registered', { pluginId, point });
            },
            unregisterExtension: (point, entry) => {
                const global = this.extensions.get(point);
                if (global) {
                    const idx = global.indexOf(entry);
                    if (idx >= 0)
                        global.splice(idx, 1);
                }
                const pluginExts = instance.extensions.get(point);
                if (pluginExts) {
                    const idx = pluginExts.indexOf(entry);
                    if (idx >= 0)
                        pluginExts.splice(idx, 1);
                }
            },
        };
    }
    /* ── Lifecycle ──────────────────────────────────────────────── */
    /** Deactivate and remove all plugins. */
    async dispose() {
        const ids = Array.from(this.plugins.keys());
        for (const id of ids) {
            try {
                await this.uninstall(id);
            }
            catch (e) {
                console.warn(`Plugin '${id}' dispose error:`, e);
            }
        }
        this.extensions.clear();
    }
}
const PLUGIN_TEMPLATES = [
    {
        type: 'component',
        name: 'Custom Component Plugin',
        description: 'Template for a plugin that adds a new ECS component type.',
        code: `import type { PluginDefinition, PluginContext } from '@nice2dev/game-engine';

const plugin: PluginDefinition = {
  manifest: {
    id: 'com.example.health-component',
    name: 'Health Component',
    version: '1.0.0',
    description: 'Adds a Health component to the ECS.',
    author: 'Your Name',
    types: ['component'],
    engineVersion: '>=0.1.0',
  },
  activate(ctx: PluginContext) {
    ctx.registerExtension('component-type', {
      type: 'Health',
      defaults: { current: 100, max: 100, regeneration: 0 },
    });
    ctx.log('Health component registered');
  },
  deactivate(ctx: PluginContext) {
    ctx.log('Health component unregistered');
  },
};

export default plugin;`,
    },
    {
        type: 'system',
        name: 'Custom System Plugin',
        description: 'Template for a plugin that adds a new ECS system.',
        code: `import type { PluginDefinition, PluginContext } from '@nice2dev/game-engine';

const plugin: PluginDefinition = {
  manifest: {
    id: 'com.example.damage-system',
    name: 'Damage System',
    version: '1.0.0',
    description: 'Processes damage events and updates health.',
    author: 'Your Name',
    types: ['system'],
    engineVersion: '>=0.1.0',
  },
  activate(ctx: PluginContext) {
    ctx.registerExtension('system-type', {
      name: 'DamageSystem',
      priority: 50,
      update: (world: unknown, dt: number) => {
        // Process damage events
      },
    });
    ctx.log('DamageSystem registered');
  },
};

export default plugin;`,
    },
    {
        type: 'node',
        name: 'Custom Node Plugin',
        description: 'Template for a plugin that adds visual scripting nodes.',
        code: `import type { PluginDefinition, PluginContext } from '@nice2dev/game-engine';

const plugin: PluginDefinition = {
  manifest: {
    id: 'com.example.timer-nodes',
    name: 'Timer Nodes',
    version: '1.0.0',
    description: 'Adds timer and delay nodes to visual scripting.',
    author: 'Your Name',
    types: ['node'],
    engineVersion: '>=0.1.0',
  },
  activate(ctx: PluginContext) {
    ctx.registerExtension('node-type', {
      type: 'wait-seconds',
      name: 'Wait Seconds',
      category: 'flow',
      description: 'Waits for a specified number of seconds.',
      ports: [
        { id: 'flow-in', name: 'In', direction: 'input', dataType: 'flow' },
        { id: 'seconds', name: 'Seconds', direction: 'input', dataType: 'number', defaultValue: 1 },
        { id: 'flow-out', name: 'Out', direction: 'output', dataType: 'flow' },
      ],
      execute: (inputs: Record<string, unknown>) => {
        // Timer logic here
        return {};
      },
    });
    ctx.log('Timer nodes registered');
  },
};

export default plugin;`,
    },
    {
        type: 'editor-panel',
        name: 'Custom Editor Panel Plugin',
        description: 'Template for a plugin that adds a new panel to the editor.',
        code: `import type { PluginDefinition, PluginContext } from '@nice2dev/game-engine';

const plugin: PluginDefinition = {
  manifest: {
    id: 'com.example.performance-panel',
    name: 'Performance Panel',
    version: '1.0.0',
    description: 'Adds a performance monitoring panel to the editor.',
    author: 'Your Name',
    types: ['editor-panel'],
    engineVersion: '>=0.1.0',
  },
  activate(ctx: PluginContext) {
    ctx.registerExtension('editor-panel', {
      id: 'perf-panel',
      title: 'Performance',
      icon: '📊',
      position: 'right',
      render: () => null, // React component
    });
    ctx.log('Performance panel registered');
  },
};

export default plugin;`,
    },
    {
        type: 'template',
        name: 'Game Template Plugin',
        description: 'Template for a plugin that adds a new game template.',
        code: `import type { PluginDefinition, PluginContext } from '@nice2dev/game-engine';

const plugin: PluginDefinition = {
  manifest: {
    id: 'com.example.infinite-runner',
    name: 'Infinite Runner Template',
    version: '1.0.0',
    description: 'Adds an infinite runner game template.',
    author: 'Your Name',
    types: ['template'],
    engineVersion: '>=0.1.0',
  },
  activate(ctx: PluginContext) {
    ctx.registerExtension('game-template', {
      id: 'infinite-runner',
      name: 'Infinite Runner',
      description: 'An auto-scrolling endless runner game.',
      category: 'action',
      difficulty: 'beginner',
      setup: (engine: unknown) => {
        // Initialize template
      },
    });
    ctx.log('Infinite Runner template registered');
  },
};

export default plugin;`,
    },
];
/** Offline-capable marketplace API (can be backed by REST later). */
class PluginMarketplace {
    constructor() {
        this.catalog = [];
    }
    /** Register entries in the catalog (for offline/bundled). */
    addEntries(entries) {
        for (const entry of entries) {
            const idx = this.catalog.findIndex(e => e.id === entry.id);
            if (idx >= 0) {
                this.catalog[idx] = entry;
            }
            else {
                this.catalog.push(entry);
            }
        }
    }
    /** Search the marketplace. */
    search(query, options = {}) {
        const { type, free, page = 0, pageSize = 20 } = options;
        const q = query.toLowerCase();
        let results = this.catalog.filter(e => {
            if (type && !e.types.includes(type))
                return false;
            if (free !== undefined && e.free !== free)
                return false;
            if (q.length > 0) {
                return e.name.toLowerCase().includes(q) ||
                    e.description.toLowerCase().includes(q) ||
                    e.keywords.some(k => k.toLowerCase().includes(q)) ||
                    e.author.toLowerCase().includes(q);
            }
            return true;
        });
        // Sort by rating then downloads
        results.sort((a, b) => b.rating - a.rating || b.downloads - a.downloads);
        const total = results.length;
        const start = page * pageSize;
        results = results.slice(start, start + pageSize);
        return { entries: results, total, page, pageSize };
    }
    /** Get a single entry by ID. */
    getEntry(id) {
        return this.catalog.find(e => e.id === id);
    }
    /** Featured / top-rated plugins. */
    getFeatured(count = 5) {
        return this.catalog
            .slice()
            .sort((a, b) => b.rating - a.rating || b.downloads - a.downloads)
            .slice(0, count);
    }
}

export { PLUGIN_TEMPLATES, PluginManager, PluginMarketplace };
//# sourceMappingURL=PluginSDK.js.map
