import { describe, it, expect } from 'vitest';
import {
  validateTipTapDocument,
  tipTapToPdfmeTemplate,
  substituteVariables,
  buildContentInputsFromDoc,
  type TipTapNode,
} from '@/lib/pdf/tiptap-to-pdfme';

// ─── Fixtures ─────────────────────────────────────────────────────────────────

function makeDoc(...children: TipTapNode[]): TipTapNode {
  return { type: 'doc', content: children };
}

function paragraph(text: string): TipTapNode {
  return {
    type: 'paragraph',
    content: [{ type: 'text', text }],
  };
}

function heading(level: number, text: string): TipTapNode {
  return {
    type: 'heading',
    attrs: { level },
    content: [{ type: 'text', text }],
  };
}

function bulletList(...items: string[]): TipTapNode {
  return {
    type: 'bulletList',
    content: items.map((item) => ({
      type: 'listItem',
      content: [paragraph(item)],
    })),
  };
}

// ─── validateTipTapDocument ───────────────────────────────────────────────────

describe('validateTipTapDocument', () => {
  it('returns empty array for a valid doc with only a paragraph', () => {
    const doc = makeDoc(paragraph('Hello world'));
    expect(validateTipTapDocument(doc)).toEqual([]);
  });

  it('returns empty array for a doc with heading + paragraph', () => {
    const doc = makeDoc(heading(1, 'Title'), paragraph('Body text'));
    expect(validateTipTapDocument(doc)).toEqual([]);
  });

  it('returns empty array for a doc with bulletList', () => {
    const doc = makeDoc(bulletList('Item 1', 'Item 2'));
    expect(validateTipTapDocument(doc)).toEqual([]);
  });

  it('returns ["blockquote"] when doc contains a blockquote', () => {
    const doc = makeDoc(
      paragraph('Before'),
      { type: 'blockquote', content: [paragraph('Quoted')] },
    );
    const errors = validateTipTapDocument(doc);
    expect(errors).toContain('blockquote');
  });

  it('returns ["codeBlock"] when doc contains a codeBlock', () => {
    const doc = makeDoc(
      paragraph('Before'),
      { type: 'codeBlock', content: [{ type: 'text', text: 'const x = 1;' }] },
    );
    const errors = validateTipTapDocument(doc);
    expect(errors).toContain('codeBlock');
  });

  it('returns multiple unsupported types without duplicates', () => {
    const doc = makeDoc(
      { type: 'blockquote', content: [] },
      { type: 'codeBlock', content: [] },
      { type: 'blockquote', content: [] }, // duplicate — should only appear once
    );
    const errors = validateTipTapDocument(doc);
    expect(errors).toContain('blockquote');
    expect(errors).toContain('codeBlock');
    expect(errors.filter((e) => e === 'blockquote')).toHaveLength(1);
  });

  it('detects unsupported nodes nested inside valid nodes', () => {
    const doc = makeDoc({
      type: 'paragraph',
      content: [{ type: 'blockquote', content: [] }],
    });
    expect(validateTipTapDocument(doc)).toContain('blockquote');
  });
});

// ─── tipTapToPdfmeTemplate ────────────────────────────────────────────────────

describe('tipTapToPdfmeTemplate', () => {
  it('returns a template with a schemas array', () => {
    const doc = makeDoc(paragraph('Hello'));
    const template = tipTapToPdfmeTemplate(doc);
    expect(template).toHaveProperty('schemas');
    expect(Array.isArray(template.schemas)).toBe(true);
  });

  it('produces one schema field per paragraph', () => {
    const doc = makeDoc(paragraph('One'), paragraph('Two'), paragraph('Three'));
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat();
    expect(allFields).toHaveLength(3);
  });

  it('heading + paragraph → 2 schema fields', () => {
    const doc = makeDoc(heading(1, 'Title'), paragraph('Body'));
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat();
    expect(allFields).toHaveLength(2);
  });

  it('bulletList with 3 items → 3 schema fields', () => {
    const doc = makeDoc(bulletList('A', 'B', 'C'));
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat();
    expect(allFields).toHaveLength(3);
  });

  it('all schema fields have type "text"', () => {
    const doc = makeDoc(heading(2, 'Sub'), paragraph('Para'), bulletList('Item'));
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat() as Array<{ type: string }>;
    for (const field of allFields) {
      expect(field.type).toBe('text');
    }
  });

  it('all schema fields have a name property', () => {
    const doc = makeDoc(paragraph('p1'), paragraph('p2'));
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat() as Array<{ name: string }>;
    for (const field of allFields) {
      expect(typeof field.name).toBe('string');
      expect(field.name.length).toBeGreaterThan(0);
    }
  });

  it('field count matches content node count (round-trip check)', () => {
    const nodeCount = 4;
    const doc = makeDoc(
      heading(1, 'H1'),
      paragraph('Para 1'),
      paragraph('Para 2'),
      bulletList('Bullet'),
    );
    const template = tipTapToPdfmeTemplate(doc);
    const allFields = template.schemas.flat();
    expect(allFields).toHaveLength(nodeCount);
  });
});

// ─── substituteVariables ──────────────────────────────────────────────────────

describe('substituteVariables', () => {
  it('replaces a single variable token', () => {
    const result = substituteVariables('Hello {{client.name}}', { 'client.name': 'Alice' });
    expect(result).toBe('Hello Alice');
  });

  it('replaces multiple variable tokens', () => {
    const result = substituteVariables('{{client.name}} at {{port.name}}', {
      'client.name': 'Alice',
      'port.name': 'Port Nimara',
    });
    expect(result).toBe('Alice at Port Nimara');
  });

  it('leaves unmatched tokens as-is', () => {
    const result = substituteVariables('Hello {{client.name}}', {});
    expect(result).toBe('Hello {{client.name}}');
  });

  it('handles whitespace inside token braces', () => {
    const result = substituteVariables('Hello {{ client.name }}', { 'client.name': 'Bob' });
    expect(result).toBe('Hello Bob');
  });

  it('replaces the same token multiple times', () => {
    const result = substituteVariables('{{x}} and {{x}}', { x: 'yes' });
    expect(result).toBe('yes and yes');
  });
});

// ─── buildContentInputsFromDoc ────────────────────────────────────────────────

describe('buildContentInputsFromDoc', () => {
  it('returns an array of records keyed by schema field names', () => {
    const doc = makeDoc(paragraph('Hello'), paragraph('World'));
    const template = tipTapToPdfmeTemplate(doc);
    const inputs = buildContentInputsFromDoc(doc, template);

    expect(Array.isArray(inputs)).toBe(true);
    expect(inputs).toHaveLength(template.schemas.length);

    const allFieldNames = (template.schemas.flat() as Array<{ name: string }>).map((f) => f.name);
    const allInputKeys = inputs.flatMap((record) => Object.keys(record));
    for (const name of allFieldNames) {
      expect(allInputKeys).toContain(name);
    }
  });

  it('input count matches schema field count', () => {
    const doc = makeDoc(heading(1, 'H'), paragraph('P1'), paragraph('P2'));
    const template = tipTapToPdfmeTemplate(doc);
    const inputs = buildContentInputsFromDoc(doc, template);

    const totalFields = template.schemas.reduce((acc, page) => acc + page.length, 0);
    const totalInputs = inputs.reduce((acc, record) => acc + Object.keys(record).length, 0);
    expect(totalInputs).toBe(totalFields);
  });
});
