import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { getStage, listStages, loadCustomStages, clearCustomStages } from '../src/proxymodel/stage-registry.js';

describe('stage-registry', () => {
  afterEach(() => {
    clearCustomStages();
  });

  describe('getStage', () => {
    it('returns built-in passthrough', () => {
      const stage = getStage('passthrough');
      expect(stage).not.toBeNull();
      expect(typeof stage).toBe('function');
    });

    it('returns built-in paginate', () => {
      expect(getStage('paginate')).not.toBeNull();
    });

    it('returns built-in section-split', () => {
      expect(getStage('section-split')).not.toBeNull();
    });

    it('returns built-in summarize-tree', () => {
      expect(getStage('summarize-tree')).not.toBeNull();
    });

    it('returns null for unknown stage', () => {
      expect(getStage('nonexistent')).toBeNull();
    });
  });

  describe('listStages', () => {
    it('lists all built-in stages', () => {
      const stages = listStages();
      expect(stages).toHaveLength(4);
      const names = stages.map((s) => s.name);
      expect(names).toContain('passthrough');
      expect(names).toContain('paginate');
      expect(names).toContain('section-split');
      expect(names).toContain('summarize-tree');
    });

    it('all built-ins show source as built-in', () => {
      const stages = listStages();
      for (const stage of stages) {
        expect(stage.source).toBe('built-in');
      }
    });
  });

  describe('loadCustomStages', () => {
    let tempDir: string;

    beforeEach(async () => {
      tempDir = await mkdtemp(join(tmpdir(), 'stages-test-'));
    });

    afterEach(async () => {
      clearCustomStages();
      await rm(tempDir, { recursive: true, force: true });
    });

    it('loads .js files from directory', async () => {
      // Write a simple stage module
      await writeFile(
        join(tempDir, 'echo.js'),
        'export default async function(content, ctx) { return { content: "echo:" + content }; }',
      );
      await loadCustomStages(tempDir);
      const stage = getStage('echo');
      expect(stage).not.toBeNull();
    });

    it('custom stage overrides built-in in listStages', async () => {
      await writeFile(
        join(tempDir, 'passthrough.js'),
        'export default async function(content) { return { content }; }',
      );
      await loadCustomStages(tempDir);
      const stages = listStages();
      const pt = stages.find((s) => s.name === 'passthrough');
      expect(pt?.source).toBe('local');
    });

    it('custom stages appear in listStages', async () => {
      await writeFile(
        join(tempDir, 'custom.js'),
        'export default async function(content) { return { content }; }',
      );
      await loadCustomStages(tempDir);
      const stages = listStages();
      const custom = stages.find((s) => s.name === 'custom');
      expect(custom).toBeDefined();
      expect(custom?.source).toBe('local');
    });

    it('skips non-.js files', async () => {
      await writeFile(join(tempDir, 'readme.md'), '# Readme');
      await loadCustomStages(tempDir);
      expect(listStages()).toHaveLength(4); // only built-ins
    });

    it('handles missing directory', async () => {
      await loadCustomStages(join(tempDir, 'nonexistent'));
      expect(listStages()).toHaveLength(4); // only built-ins
    });

    it('clearCustomStages removes loaded stages', async () => {
      await writeFile(
        join(tempDir, 'temp.js'),
        'export default async function(content) { return { content }; }',
      );
      await loadCustomStages(tempDir);
      expect(getStage('temp')).not.toBeNull();
      clearCustomStages();
      expect(getStage('temp')).toBeNull();
    });
  });
});
