import { describe, expect, it } from 'vitest';
import {
  assembleFeatureStubs,
  assembleStubFile,
  fallbackDecl,
  parsesClean,
} from '../src/core/stubScaffold';

describe("stub scaffolding — structure is the harness's job", () => {
  it('zero model input: every contracted symbol exported, file parses', () => {
    const content = assembleStubFile('F1', 'src/store.ts', ['createArticle', 'listArticles'], undefined);
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/export function createArticle/);
    expect(content).toMatch(/export function listArticles/);
    expect(content).toMatch(/throw new Error/);
  });

  it('good signatures render per kind and parse', () => {
    const content = assembleStubFile('F1', 'src/model.ts', ['Article', 'Status', 'ArticleManager', 'MAX_DEPTH', 'slugify'], {
      path: 'src/model.ts',
      imports: [],
      signatures: [
        { symbol: 'Article', kind: 'interface', sig: '{ id: string; title: string }' },
        { symbol: 'Status', kind: 'type', sig: "'draft' | 'published'" },
        { symbol: 'ArticleManager', kind: 'class', sig: '{ create(id: string): Article; count: number }' },
        { symbol: 'MAX_DEPTH', kind: 'const', sig: 'number' },
        { symbol: 'slugify', kind: 'function', sig: '(title: string): string' },
      ],
    });
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/export interface Article \{ id: string; title: string \}/);
    expect(content).toMatch(/export type Status = 'draft' \| 'published';/);
    expect(content).toMatch(/create\(id: string\): Article \{ throw new Error/);
    expect(content).toMatch(/declare count: number;/);
    expect(content).toMatch(/export function slugify\(title: string\): string \{ throw/);
  });

  it('a malformed sig falls back for THAT symbol only; the file still parses', () => {
    const content = assembleStubFile('F1', 'src/x.ts', ['good', 'bad'], {
      path: 'src/x.ts',
      imports: [],
      signatures: [
        { symbol: 'good', kind: 'function', sig: '(n: number): number' },
        { symbol: 'bad', kind: 'function', sig: '(n: number: broken:::' },
      ],
    });
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/export function good\(n: number\): number/);
    expect(content).toMatch(/export function bad\(\.\.\.args: unknown\[\]\): never/);
  });

  it('interface fallback is empty-but-growable, never an index signature', () => {
    expect(fallbackDecl('Page', 'interface')).toBe('export interface Page {}');
  });

  it('cross-stub type imports normalize and render', () => {
    const content = assembleStubFile('F2', 'src/renderer.ts', ['render'], {
      path: 'src/renderer.ts',
      imports: [{ from: 'src/article_model.ts', symbols: ['Article'] }],
      signatures: [{ symbol: 'render', kind: 'function', sig: '(a: Article): string' }],
    });
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/import type \{ Article \} from '\.\/article_model';/);
  });

  it('assembleFeatureStubs: every owned file gets a stub; foreign paths dropped', () => {
    const stubs = assembleFeatureStubs(
      {
        id: 'F3',
        files: ['src/a.ts', 'src/b.ts'],
        exports: [
          { file: 'src/a.ts', symbol: 'alpha' },
          { file: 'src/b.ts', symbol: 'beta' },
        ],
      },
      [
        { path: 'src/a.ts', imports: [], signatures: [{ symbol: 'alpha', kind: 'function', sig: '(): void' }] },
        { path: 'src/NOT_OWNED.ts', imports: [], signatures: [{ symbol: 'x', kind: 'function', sig: '(): void' }] },
      ],
    );
    expect(stubs.map(s => s.path).sort()).toEqual(['src/a.ts', 'src/b.ts']);
    for (const s of stubs) expect(parsesClean(s.content)).toBe(true);
    expect(stubs.find(s => s.path === 'src/b.ts')!.content).toMatch(/export function beta/);
  });
});

describe('sig sanitization — no doubled names from restated context', () => {
  it('strips a restated symbol name (the lockThreadlockThread bug)', () => {
    const content = assembleStubFile('F34', 'src/thread_lock.ts', ['lockThread'], {
      path: 'src/thread_lock.ts',
      imports: [],
      signatures: [{ symbol: 'lockThread', kind: 'function', sig: 'lockThread(threadId: string): Promise<void>' }],
    });
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/export function lockThread\(threadId: string\): Promise<void>/);
    expect(content).not.toMatch(/lockThreadlockThread/);
  });

  it('strips leading export/function/async keywords', () => {
    const content = assembleStubFile('F1', 'src/x.ts', ['go'], {
      path: 'src/x.ts',
      imports: [],
      signatures: [{ symbol: 'go', kind: 'function', sig: 'export async function go(n: number): Promise<number>' }],
    });
    expect(parsesClean(content)).toBe(true);
    expect(content).toMatch(/export function go\(n: number\): Promise<number>/);
    expect(content).not.toMatch(/gogo|function function|export export/);
  });
});
