// Tests for Electrobun plugin-store — pure logic.
// Uses bun:test. Tests command registry, event bus, and permission validation.

import { describe, it, expect, beforeEach } from 'bun:test';

// ── Replicated types ──────────────────────────────────────────────────────────

interface PluginMeta {
  id: string;
  name: string;
  version: string;
  description: string;
  main: string;
  permissions: string[];
  allowedOrigins?: string[];
  maxRuntime?: number;
}

interface PluginCommand {
  pluginId: string;
  label: string;
  callback: () => void;
}

// ── Replicated event bus ────────────────────────────────────────────────────

type EventHandler = (data: unknown) => void;

function createEventBus() {
  const listeners = new Map<string, Set<EventHandler>>();
  return {
    on(event: string, handler: EventHandler): void {
      let set = listeners.get(event);
      if (!set) { set = new Set(); listeners.set(event, set); }
      set.add(handler);
    },
    off(event: string, handler: EventHandler): void {
      listeners.get(event)?.delete(handler);
    },
    emit(event: string, data: unknown): void {
      const set = listeners.get(event);
      if (!set) return;
      for (const handler of set) {
        try { handler(data); } catch { /* swallow */ }
      }
    },
    listenerCount(event: string): number {
      return listeners.get(event)?.size ?? 0;
    },
  };
}

// ── Replicated command registry ──────────────────────────────────────────────

function createCommandRegistry() {
  let commands: PluginCommand[] = [];
  return {
    add(pluginId: string, label: string, callback: () => void): void {
      commands = [...commands, { pluginId, label, callback }];
    },
    remove(pluginId: string): void {
      commands = commands.filter(c => c.pluginId !== pluginId);
    },
    getAll(): PluginCommand[] { return commands; },
    getByPlugin(pluginId: string): PluginCommand[] {
      return commands.filter(c => c.pluginId === pluginId);
    },
  };
}

// ── Permission validation (replicated from plugin-host.ts) ──────────────────

const VALID_PERMISSIONS = new Set(['palette', 'notifications', 'messages', 'events', 'network']);

function validatePermissions(perms: string[]): { valid: boolean; invalid: string[] } {
  const invalid = perms.filter(p => !VALID_PERMISSIONS.has(p));
  return { valid: invalid.length === 0, invalid };
}

// ── Tests ───────────────────────────────────────────────────────────────────

describe('command registry', () => {
  let registry: ReturnType<typeof createCommandRegistry>;

  beforeEach(() => {
    registry = createCommandRegistry();
  });

  it('starts empty', () => {
    expect(registry.getAll()).toHaveLength(0);
  });

  it('add registers a command', () => {
    registry.add('my-plugin', 'Say Hello', () => {});
    expect(registry.getAll()).toHaveLength(1);
    expect(registry.getAll()[0].label).toBe('Say Hello');
    expect(registry.getAll()[0].pluginId).toBe('my-plugin');
  });

  it('remove clears commands for a plugin', () => {
    registry.add('p1', 'Cmd A', () => {});
    registry.add('p1', 'Cmd B', () => {});
    registry.add('p2', 'Cmd C', () => {});
    registry.remove('p1');
    expect(registry.getAll()).toHaveLength(1);
    expect(registry.getAll()[0].pluginId).toBe('p2');
  });

  it('getByPlugin filters correctly', () => {
    registry.add('p1', 'A', () => {});
    registry.add('p2', 'B', () => {});
    registry.add('p1', 'C', () => {});
    expect(registry.getByPlugin('p1')).toHaveLength(2);
    expect(registry.getByPlugin('p2')).toHaveLength(1);
    expect(registry.getByPlugin('p3')).toHaveLength(0);
  });
});

describe('event bus', () => {
  let bus: ReturnType<typeof createEventBus>;

  beforeEach(() => {
    bus = createEventBus();
  });

  it('on registers listener', () => {
    bus.on('test', () => {});
    expect(bus.listenerCount('test')).toBe(1);
  });

  it('emit calls registered handlers', () => {
    let received: unknown = null;
    bus.on('data', (d) => { received = d; });
    bus.emit('data', { value: 42 });
    expect(received).toEqual({ value: 42 });
  });

  it('off removes listener', () => {
    const handler = () => {};
    bus.on('event', handler);
    bus.off('event', handler);
    expect(bus.listenerCount('event')).toBe(0);
  });

  it('emit does not throw for unregistered events', () => {
    expect(() => bus.emit('nonexistent', null)).not.toThrow();
  });

  it('handler error is swallowed', () => {
    bus.on('crash', () => { throw new Error('boom'); });
    expect(() => bus.emit('crash', null)).not.toThrow();
  });
});

describe('permission validation', () => {
  it('accepts all valid permissions', () => {
    const result = validatePermissions(['palette', 'notifications', 'messages', 'events', 'network']);
    expect(result.valid).toBe(true);
    expect(result.invalid).toHaveLength(0);
  });

  it('rejects unknown permissions', () => {
    const result = validatePermissions(['palette', 'filesystem', 'exec']);
    expect(result.valid).toBe(false);
    expect(result.invalid).toEqual(['filesystem', 'exec']);
  });

  it('accepts empty permissions list', () => {
    const result = validatePermissions([]);
    expect(result.valid).toBe(true);
  });
});

describe('plugin meta validation', () => {
  it('maxRuntime defaults to 30 seconds', () => {
    const meta: PluginMeta = {
      id: 'test', name: 'Test', version: '1.0', description: 'desc',
      main: 'index.js', permissions: [],
    };
    const maxRuntime = (meta.maxRuntime ?? 30) * 1000;
    expect(maxRuntime).toBe(30_000);
  });

  it('allowedOrigins defaults to empty array', () => {
    const meta: PluginMeta = {
      id: 'test', name: 'Test', version: '1.0', description: 'desc',
      main: 'index.js', permissions: ['network'],
    };
    expect(meta.allowedOrigins ?? []).toEqual([]);
  });
});
