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

/* ────────────────────────────────────────────────────────────────
   Plugin Test Kit — Testing framework for 3rd party plugins
   
   Sprint L9: Automated plugin testing with sandboxed context,
   lifecycle assertions, extension point verification, and
   snapshot testing for plugin storage.
──────────────────────────────────────────────────────────────── */
/* ══════════════════════════════════════════════════════════════
   In-Memory Test Storage
   ══════════════════════════════════════════════════════════════ */
class TestPluginStorage {
    constructor() {
        this.data = new Map();
    }
    get(key) {
        return this.data.get(key);
    }
    set(key, value) {
        this.data.set(key, value);
    }
    remove(key) {
        this.data.delete(key);
    }
    clear() {
        this.data.clear();
    }
    keys() {
        return Array.from(this.data.keys());
    }
    /** Snapshot current storage state for assertions. */
    snapshot() {
        const result = {};
        for (const [k, v] of this.data) {
            result[k] = v;
        }
        return result;
    }
    get size() {
        return this.data.size;
    }
}
/* ══════════════════════════════════════════════════════════════
   Test Plugin Context — sandboxed context for testing
   ══════════════════════════════════════════════════════════════ */
class TestPluginContext {
    constructor(manifest) {
        this.logs = [];
        this.warnings = [];
        this.registeredExtensions = new Map();
        this.log = (message) => {
            this.logs.push(message);
        };
        this.warn = (message) => {
            this.warnings.push(message);
        };
        this.registerExtension = (point, entry) => {
            let list = this.registeredExtensions.get(point);
            if (!list) {
                list = [];
                this.registeredExtensions.set(point, list);
            }
            list.push(entry);
        };
        this.unregisterExtension = (point, entry) => {
            const list = this.registeredExtensions.get(point);
            if (list) {
                const idx = list.indexOf(entry);
                if (idx >= 0)
                    list.splice(idx, 1);
            }
        };
        this.manifest = manifest;
        this.events = new EventBus();
        this.storage = new TestPluginStorage();
    }
    /* ── Assertion helpers ─────────────────────────────────────── */
    /** Get all extensions registered for a given point. */
    getExtensions(point) {
        var _a;
        return ((_a = this.registeredExtensions.get(point)) !== null && _a !== void 0 ? _a : []);
    }
    /** Count extensions for a given point. */
    extensionCount(point) {
        var _a;
        return ((_a = this.registeredExtensions.get(point)) !== null && _a !== void 0 ? _a : []).length;
    }
    /** Check if any extension was registered for a given point. */
    hasExtension(point) {
        return this.extensionCount(point) > 0;
    }
    /** Get storage snapshot for assertion/comparison. */
    storageSnapshot() {
        return this.storage.snapshot();
    }
    /** Reset context to fresh state (between tests). */
    reset() {
        this.logs.length = 0;
        this.warnings.length = 0;
        this.registeredExtensions.clear();
        this.storage.clear();
    }
}
/* ══════════════════════════════════════════════════════════════
   Assertion Utilities
   ══════════════════════════════════════════════════════════════ */
class PluginAssertionError extends Error {
    constructor(message) {
        super(message);
        this.name = 'PluginAssertionError';
    }
}
const pluginAssert = {
    /** Assert value is truthy. */
    ok(value, message) {
        if (!value) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Expected truthy, got ${String(value)}`);
        }
    },
    /** Assert strict equality. */
    equal(actual, expected, message) {
        if (actual !== expected) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Expected ${String(expected)}, got ${String(actual)}`);
        }
    },
    /** Assert not equal. */
    notEqual(actual, notExpected, message) {
        if (actual === notExpected) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Expected not ${String(notExpected)}`);
        }
    },
    /** Assert deep equality (JSON comparison). */
    deepEqual(actual, expected, message) {
        const a = JSON.stringify(actual);
        const b = JSON.stringify(expected);
        if (a !== b) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Deep equality failed.\nActual: ${a}\nExpected: ${b}`);
        }
    },
    /** Assert that a function throws. */
    throws(fn, message) {
        let threw = false;
        try {
            fn();
        }
        catch (_a) {
            threw = true;
        }
        if (!threw) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : 'Expected function to throw');
        }
    },
    /** Assert async function throws. */
    async throwsAsync(fn, message) {
        let threw = false;
        try {
            await fn();
        }
        catch (_a) {
            threw = true;
        }
        if (!threw) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : 'Expected async function to throw');
        }
    },
    /** Assert array contains element. */
    contains(arr, item, message) {
        if (arr.indexOf(item) < 0) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Array does not contain ${String(item)}`);
        }
    },
    /** Assert string includes substring. */
    includes(str, substr, message) {
        if (!str.includes(substr)) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `"${str}" does not include "${substr}"`);
        }
    },
    /** Assert extension was registered. */
    hasExtension(ctx, point, message) {
        if (!ctx.hasExtension(point)) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `No extension registered for "${point}"`);
        }
    },
    /** Assert extension count. */
    extensionCount(ctx, point, expected, message) {
        const actual = ctx.extensionCount(point);
        if (actual !== expected) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `Expected ${expected} extensions for "${point}", got ${actual}`);
        }
    },
    /** Assert log message was emitted. */
    logged(ctx, substr, message) {
        const found = ctx.logs.some(l => l.includes(substr));
        if (!found) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `No log message containing "${substr}". Logs: [${ctx.logs.join(', ')}]`);
        }
    },
    /** Assert warning was emitted. */
    warned(ctx, substr, message) {
        const found = ctx.warnings.some(w => w.includes(substr));
        if (!found) {
            throw new PluginAssertionError(message !== null && message !== void 0 ? message : `No warning containing "${substr}". Warnings: [${ctx.warnings.join(', ')}]`);
        }
    },
};
/* ══════════════════════════════════════════════════════════════
   Plugin Test Runner
   ══════════════════════════════════════════════════════════════ */
class PluginTestRunner {
    constructor() {
        this.tests = [];
        this._beforeEach = null;
        this._afterEach = null;
    }
    /** Add a test case. */
    test(name, fn) {
        this.tests.push({ name, fn });
        return this;
    }
    /** Add a skipped test case. */
    skip(name, fn) {
        this.tests.push({ name, fn, skip: true });
        return this;
    }
    /** Run something before each test. */
    beforeEach(fn) {
        this._beforeEach = fn;
        return this;
    }
    /** Run something after each test. */
    afterEach(fn) {
        this._afterEach = fn;
        return this;
    }
    /** Run all tests against a plugin. */
    async run(plugin, suiteName) {
        const manifest = plugin.manifest;
        const ctx = new TestPluginContext(manifest);
        const results = [];
        let passed = 0;
        let failed = 0;
        let skipped = 0;
        const suiteStart = performance.now();
        for (const tc of this.tests) {
            if (tc.skip) {
                results.push({ name: tc.name, status: 'skipped', duration: 0 });
                skipped++;
                continue;
            }
            ctx.reset();
            const start = performance.now();
            try {
                if (this._beforeEach)
                    await this._beforeEach(ctx);
                await tc.fn(ctx);
                if (this._afterEach)
                    await this._afterEach(ctx);
                results.push({
                    name: tc.name,
                    status: 'passed',
                    duration: performance.now() - start,
                });
                passed++;
            }
            catch (err) {
                results.push({
                    name: tc.name,
                    status: 'failed',
                    duration: performance.now() - start,
                    error: err instanceof Error ? err.message : String(err),
                });
                failed++;
            }
        }
        return {
            pluginId: manifest.id,
            suiteName: suiteName !== null && suiteName !== void 0 ? suiteName : `${manifest.name} tests`,
            results,
            passed,
            failed,
            skipped,
            totalDuration: performance.now() - suiteStart,
        };
    }
}
/* ══════════════════════════════════════════════════════════════
   Convenience: Quick Plugin Test
   ══════════════════════════════════════════════════════════════ */
/** Create a default manifest for testing. */
function createTestManifest(overrides) {
    return {
        id: 'com.test.plugin',
        name: 'Test Plugin',
        version: '1.0.0',
        description: 'A plugin for testing',
        author: 'Test',
        types: ['component'],
        engineVersion: '>=0.1.0',
        ...overrides,
    };
}
/** Quick lifecycle test — activate, verify, deactivate. */
async function testPluginLifecycle(plugin) {
    const runner = new PluginTestRunner();
    runner
        .test('should activate without errors', async (ctx) => {
        await plugin.activate(ctx);
    })
        .test('should register at least one extension', async (ctx) => {
        await plugin.activate(ctx);
        let total = 0;
        for (const [, entries] of ctx.registeredExtensions) {
            total += entries.length;
        }
        pluginAssert.ok(total > 0, 'Plugin should register at least one extension');
    })
        .test('should deactivate without errors', async (ctx) => {
        await plugin.activate(ctx);
        if (plugin.deactivate) {
            await plugin.deactivate(ctx);
        }
    })
        .test('should survive activate → deactivate → activate cycle', async (ctx) => {
        await plugin.activate(ctx);
        if (plugin.deactivate)
            await plugin.deactivate(ctx);
        ctx.reset();
        await plugin.activate(ctx);
    })
        .test('should produce log output', async (ctx) => {
        await plugin.activate(ctx);
        pluginAssert.ok(ctx.logs.length > 0, 'Plugin should log something on activation');
    });
    return runner.run(plugin, `${plugin.manifest.name} lifecycle`);
}
/** Format test results as a readable string. */
function formatTestResults(suite) {
    const lines = [
        `━━━ ${suite.suiteName} (${suite.pluginId}) ━━━`,
        '',
    ];
    for (const r of suite.results) {
        const icon = r.status === 'passed' ? '✓' : r.status === 'failed' ? '✗' : '○';
        const ms = r.duration.toFixed(1);
        lines.push(`  ${icon} ${r.name} (${ms}ms)`);
        if (r.error) {
            lines.push(`    → ${r.error}`);
        }
    }
    lines.push('');
    lines.push(`  ${suite.passed} passed, ${suite.failed} failed, ${suite.skipped} skipped`);
    lines.push(`  Total: ${suite.totalDuration.toFixed(1)}ms`);
    lines.push('');
    return lines.join('\n');
}

export { PluginAssertionError, PluginTestRunner, TestPluginContext, TestPluginStorage, createTestManifest, formatTestResults, pluginAssert, testPluginLifecycle };
//# sourceMappingURL=PluginTestKit.js.map
