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

function makeContext(overrides: Partial<EmitContext> = {}): EmitContext {
  const post: ModelSpec = {
    name: 'Post',
    attributes: {
      id: { type: 'UUID', required: true },
      title: { type: 'String', required: true },
      body: { type: 'Text', required: false },
      status: { type: 'String', required: false, values: ['draft', 'live', 'archived'] },
      createdAt: { type: 'DateTime', required: false },
    },
  };

  return {
    view: { type: 'dashboard', model: 'Post' },
    viewName: 'PostDashboardView',
    model: post,
    modelSchemas: { Post: post },
    renderBody: composeDashboardBody,
    ...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('composeDashboardBody — metrics row', () => {
  it('emits a total-count card', () => {
    const body = composeDashboardBody(makeContext());
    expect(body).toContain('Total posts');
    expect(body).toContain('{items.length}');
  });

  it('emits a per-value breakdown card for each enum value when a small enum exists', () => {
    const body = composeDashboardBody(makeContext());
    // The `status` attribute has 3 values. Each gets a card.
    expect(body).toContain('Status: Draft');
    expect(body).toContain('Status: Live');
    expect(body).toContain('Status: Archived');
    // Filter expressions for counts
    expect(body).toContain(`i.status === "draft"`);
    expect(body).toContain(`i.status === "live"`);
    expect(body).toContain(`i.status === "archived"`);
  });

  it('falls back to a placeholder card when no enum fields exist', () => {
    const plain: ModelSpec = {
      name: 'Plain',
      attributes: {
        id: { type: 'UUID' },
        name: { type: 'String' },
      },
    };
    const body = composeDashboardBody(makeContext({
      model: plain,
      view: { type: 'dashboard', model: 'Plain' },
      viewName: 'PlainDashboardView',
      modelSchemas: { Plain: plain },
    }));
    expect(body).toContain('Add a metric here');
  });

  it('skips large enums (> 6 values) to avoid card sprawl', () => {
    const huge: ModelSpec = {
      name: 'Huge',
      attributes: {
        id: { type: 'UUID' },
        tier: {
          type: 'String',
          values: ['s', 'a', 'b', 'c', 'd', 'e', 'f'], // 7 → should be skipped
        },
      },
    };
    const body = composeDashboardBody(makeContext({
      model: huge,
      view: { type: 'dashboard', model: 'Huge' },
      viewName: 'HugeDashboardView',
      modelSchemas: { Huge: huge },
    }));
    expect(body).not.toContain('Tier: S');
    expect(body).toContain('Add a metric here');  // placeholder kicked in
  });
});

describe('composeDashboardBody — recent preview', () => {
  it('emits a table with business-field columns (metadata excluded)', () => {
    const body = composeDashboardBody(makeContext());
    expect(body).toContain('Recent Posts');
    expect(body).toContain('>Title<');
    expect(body).toContain('>Body<');
    expect(body).toContain('>Status<');
    // metadata excluded
    expect(body).not.toContain('>Id<');
    expect(body).not.toContain('>Created At<');
  });

  it('maps over `preview` (from the skeleton), not `items`', () => {
    const body = composeDashboardBody(makeContext());
    expect(body).toContain('{preview.map((item, idx) =>');
  });

  it('handles empty-list state with a spanning row', () => {
    const body = composeDashboardBody(makeContext());
    expect(body).toContain('{preview.length === 0 && (');
    expect(body).toContain('No records yet.');
  });
});

describe('composeDashboardBody — TODO comments', () => {
  it('flags the extension point for aggregation metrics', () => {
    const body = composeDashboardBody(makeContext());
    // Phase 2: the TODO now sits in the 'charts' section (not the
    // 'metrics' row) since metrics are rendered as count cards.
    expect(body).toContain('TODO: add aggregation charts');
  });
});

describe('emitView wired to composeDashboardBody — end-to-end', () => {
  it('produces a complete PostDashboardView.tsx that parses', () => {
    const source = emitView(makeContext());
    assertValidTsx(source, 'PostDashboardView');
  });

  it('wires the skeleton: component, hooks, preview memo, body', () => {
    const source = emitView(makeContext());
    expect(source).toContain('export function PostDashboardView');
    expect(source).toContain('usePostsQuery');
    expect(source).toContain('Post dashboard');
    expect(source).toContain('items.slice(0, previewLimit)');
    expect(source).toContain('Total posts');
    expect(source).toContain('Recent Posts');
    expect(source).not.toMatch(/\{\{[A-Z_]+\}\}/);
  });
});
