import { describe, it, expect } from 'vitest';
import * as ts from 'typescript';
import { emitView, type EmitContext, type ModelSpec, type ViewSpec } from '../view-emitter.js';

/**
 * Build a minimal context. The `renderBody` is a stub that returns a
 * fixed Tailwind table — the point of these tests is the emitter
 * orchestration + substitutions + JSX validity, not the body's content.
 * Parity with the real Tailwind adapter is covered by Phase 3 (P3).
 */
function makeContext(overrides: Partial<EmitContext> = {}): EmitContext {
  const defaultModel: ModelSpec = {
    name: 'Post',
    attributes: {
      id: { type: 'UUID', required: true },
      title: { type: 'String', required: true },
      body: { type: 'Text', required: false },
    },
  };

  const defaultView: ViewSpec = {
    type: 'list',
    model: 'Post',
  };

  return {
    view: defaultView,
    viewName: 'PostListView',
    model: defaultModel,
    modelSchemas: { Post: defaultModel },
    // renderBody returns JSX-ready source that will be injected verbatim
    // into {{BODY}}. NOT raw HTML — that's the composer's job.
    renderBody: () =>
      '<table className="w-full"><thead><tr><th className="px-4 py-2">Title</th></tr></thead>' +
      '<tbody><tr><td className="px-4 py-2">Example</td></tr></tbody></table>',
    ...overrides,
  };
}

function assertValidTsx(source: string, label: string): void {
  const result = ts.transpileModule(source, {
    compilerOptions: {
      jsx: ts.JsxEmit.Preserve,
      target: ts.ScriptTarget.ES2022,
      module: ts.ModuleKind.ESNext,
      moduleResolution: ts.ModuleResolutionKind.Bundler,
      strict: false,
    },
    reportDiagnostics: true,
  });
  const errors = result.diagnostics?.filter(d => d.category === ts.DiagnosticCategory.Error) ?? [];
  if (errors.length > 0) {
    const message = errors
      .map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'))
      .join('\n');
    throw new Error(`${label} failed to parse as TSX:\n${message}\n\n--- source ---\n${source}`);
  }
}

describe('emitView — list', () => {
  it('produces TSX that parses cleanly', () => {
    const context = makeContext();
    const source = emitView(context);
    assertValidTsx(source, 'emitted list view');
  });

  it('substitutes model names into the skeleton', () => {
    const source = emitView(makeContext());
    expect(source).toContain('PostListView');                  // component name
    expect(source).toContain('usePostsQuery');                 // generated hook name
    expect(source).toContain('useDeletePostMutation');         // generated hook name
    expect(source).toContain("Search posts…");                 // pluralized lowercase
    expect(source).toContain('+ New Post');                    // action button label
    expect(source).toContain('No posts yet.');                 // empty state
  });

  it('substitutes a non-trivial plural correctly', () => {
    const m: ModelSpec = { name: 'Category', attributes: {} };
    const source = emitView(makeContext({
      model: m,
      view: { type: 'list', model: 'Category' },
      viewName: 'CategoryListView',
      modelSchemas: { Category: m },
    }));
    expect(source).toContain('CategoryListView');
    expect(source).toContain('useCategoriesQuery');     // "Category" → "Categories"
    expect(source).toContain('Search categories…');
  });

  it('injects the rendered body at {{BODY}}', () => {
    const source = emitView(makeContext({
      renderBody: () => '<table className="my-table"><tbody><tr><td>Custom</td></tr></tbody></table>',
    }));
    expect(source).toContain('<table className="my-table">');
    expect(source).toContain('Custom');
    // The placeholder itself should be gone.
    expect(source).not.toContain('{{BODY}}');
    // All other placeholders should be gone too.
    expect(source).not.toMatch(/\{\{[A-Z_]+\}\}/);
  });

  it('throws for an unknown view type', () => {
    expect(() =>
      emitView(makeContext({ view: { type: 'unknown-type-xyz' } }))
    ).toThrow(/No skeleton registered/);
  });
});

describe('emitView — form date-helper imports (TS6133 guard)', () => {
  // Regression: the date-formatter imports must be derived from what the form
  // body ACTUALLY renders, not from attribute types. An auto/hidden DateTime
  // field (e.g. `createdAt: DateTime auto=now`) is in the schema but the
  // form-fields resolver never renders it, so importing formatDateTimeForInput
  // off the type list produced an unused import → TS6133 → Factory B build fail.
  it('does NOT import formatDateTimeForInput when the only DateTime field is auto/hidden', () => {
    const model: ModelSpec = {
      name: 'Item',
      attributes: {
        id: { type: 'UUID', required: true },
        name: { type: 'String', required: true },
        createdAt: { type: 'DateTime', auto: 'now' },  // hidden in the form
      },
    };
    const source = emitView(makeContext({
      view: { type: 'form', model: 'Item' },
      viewName: 'ItemFormView',
      model,
      modelSchemas: { Item: model },
    }));
    expect(source).not.toContain('formatDateTimeForInput');
    assertValidTsx(source, 'auto-only DateTime form view');
  });

  it('DOES import formatDateTimeForInput when the form renders an editable DateTime field', () => {
    const model: ModelSpec = {
      name: 'Event',
      attributes: {
        id: { type: 'UUID', required: true },
        name: { type: 'String', required: true },
        startsAt: { type: 'DateTime', required: true },  // editable → rendered
      },
    };
    const source = emitView(makeContext({
      view: { type: 'form', model: 'Event' },
      viewName: 'EventFormView',
      model,
      modelSchemas: { Event: model },
    }));
    expect(source).toContain('formatDateTimeForInput');
  });
});
