import { describe, it, expect } from 'vitest';
import type {
  StageHandler,
  StageContext,
  StageResult,
  Section,
  LLMProvider,
  CacheProvider,
  StageLogger,
  ProxyModelDefinition,
  SessionController,
  SessionContext,
  ContentType,
} from '../src/proxymodel/index.js';

describe('ProxyModel type contract', () => {
  it('StageHandler can be implemented as a simple function', async () => {
    const handler: StageHandler = async (content, _ctx) => {
      return { content: content.toUpperCase() };
    };

    const ctx = createMockContext('test content');
    const result = await handler('hello', ctx);
    expect(result.content).toBe('HELLO');
  });

  it('StageResult supports sections for drill-down', async () => {
    const handler: StageHandler = async (content, _ctx) => {
      const sections: Section[] = [
        { id: 'intro', title: 'Introduction', content: 'intro text' },
        { id: 'details', title: 'Details', content: 'detail text' },
      ];
      return {
        content: '2 sections: [intro] Introduction [details] Details',
        sections,
      };
    };

    const ctx = createMockContext('long content');
    const result = await handler('long content', ctx);
    expect(result.sections).toHaveLength(2);
    expect(result.sections![0].id).toBe('intro');
    expect(result.sections![1].content).toBe('detail text');
  });

  it('Section supports nested children for hierarchical drill-down', () => {
    const section: Section = {
      id: 'security',
      title: 'Security Monitoring',
      content: 'summary',
      children: [
        { id: 'security.cameras', title: 'Camera Config', content: 'camera details' },
        { id: 'security.alerts', title: 'Alert Rules', content: 'alert details' },
      ],
    };
    expect(section.children).toHaveLength(2);
    expect(section.children![0].id).toBe('security.cameras');
  });

  it('StageResult supports metadata for metrics', async () => {
    const handler: StageHandler = async (content, _ctx) => ({
      content,
      metadata: { tokensProcessed: 150, latencyMs: 42 },
    });

    const result = await handler('test', createMockContext('test'));
    expect(result.metadata).toEqual({ tokensProcessed: 150, latencyMs: 42 });
  });

  it('StageContext provides originalContent even after prior stage changes', () => {
    const ctx = createMockContext('original text');
    // Simulate prior stage having changed the content
    expect(ctx.originalContent).toBe('original text');
  });

  it('ProxyModelDefinition has all required fields', () => {
    const model: ProxyModelDefinition = {
      name: 'subindex',
      controller: 'gate',
      controllerConfig: { byteBudget: 8192 },
      stages: [
        { type: 'section-split', config: { minSectionSize: 2000 } },
        { type: 'summarize-tree', config: { maxSummaryTokens: 200 } },
      ],
      appliesTo: ['prompt', 'toolResult'],
      cacheable: true,
      source: 'built-in',
    };
    expect(model.stages).toHaveLength(2);
    expect(model.appliesTo).toContain('prompt');
  });

  it('ContentType covers all expected types', () => {
    const types: ContentType[] = ['prompt', 'toolResult', 'resource'];
    expect(types).toHaveLength(3);
  });

  it('SessionController can be partially implemented', () => {
    const controller: SessionController = {
      async onToolsList(tools, _ctx) {
        return tools.filter((t) => t.name !== 'hidden');
      },
    };
    expect(controller.onInitialize).toBeUndefined();
    expect(controller.onToolsList).toBeDefined();
    expect(controller.onClose).toBeUndefined();
  });
});

function createMockContext(original: string): StageContext {
  const mockLlm: LLMProvider = {
    async complete(prompt) { return `mock: ${prompt.slice(0, 20)}`; },
    available() { return true; },
  };

  const mockCache: CacheProvider = {
    async getOrCompute(_key, compute) { return compute(); },
    hash(content) { return content.slice(0, 8); },
    async get() { return null; },
    async set() {},
  };

  const mockLog: StageLogger = {
    debug() {},
    info() {},
    warn() {},
    error() {},
  };

  return {
    contentType: 'prompt',
    sourceName: 'test-prompt',
    projectName: 'test-project',
    sessionId: 'sess-123',
    originalContent: original,
    llm: mockLlm,
    cache: mockCache,
    log: mockLog,
    getSystemPrompt: async (_name: string, fallback: string) => fallback,
    config: {},
  };
}
