/**
 * Integration test over the hand-authored comprehensive fixture
 * (`e2e/fixtures/block-sdt-comprehensive.docx`, generated by
 * `scripts/make-block-sdt-fixture.mjs`). It exercises every block-level
 * content-control scenario end to end through the real DOCX pipeline:
 *   - Phase 1: parse → serialize preserves all controls + unmodeled features
 *   - Phase 2: parse → toProseDoc → fromProseDoc → serialize (the edit cycle)
 *     keeps controls and the verbatim raw w:sdtPr, and appends the
 *     caret-trap trailing paragraph after a doc-final control.
 *
 * If the fixture is ever regenerated, keep the scenario count/tags in sync.
 */

import { describe, test, expect } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import JSZip from 'jszip';
import { parseDocumentBody } from '../documentParser';
import { serializeDocumentBody } from '../serializer/documentSerializer';
import { toProseDoc } from '../../prosemirror/conversion/toProseDoc';
import { fromProseDoc } from '../../prosemirror/conversion/fromProseDoc';
import type { BlockContent, BlockSdt, Document } from '../../types/document';

const FIXTURE = join(import.meta.dir, '../../../../../e2e/fixtures/block-sdt-comprehensive.docx');
const TAGS = [
  'intro',
  'grid',
  'multi',
  'outer',
  'inner',
  'locked',
  'bound',
  'repeat',
  'choice',
  'endpr',
  'last',
];

async function documentXml(): Promise<string> {
  const zip = await JSZip.loadAsync(readFileSync(FIXTURE));
  const doc = zip.file('word/document.xml');
  if (!doc) throw new Error('fixture missing word/document.xml');
  return doc.async('string');
}

function countBlockSdt(blocks: BlockContent[]): number {
  let n = 0;
  for (const b of blocks) {
    if (b.type === 'blockSdt') {
      n += 1 + countBlockSdt((b as BlockSdt).content);
    }
  }
  return n;
}

describe('comprehensive block-SDT fixture', () => {
  test('parses 10 top-level + 1 nested block SDT', async () => {
    const body = parseDocumentBody(await documentXml());
    expect(body.content.filter((b) => b.type === 'blockSdt').length).toBe(10);
    expect(countBlockSdt(body.content)).toBe(11);
  });

  test('Phase 1 parse → serialize preserves every control and unmodeled feature', async () => {
    const out = serializeDocumentBody(parseDocumentBody(await documentXml()));
    expect((out.match(/<w:sdt>/g) ?? []).length).toBe(11);
    for (const tag of TAGS) expect(out).toContain(`w:val="${tag}"`);
    expect(out).toContain('w:dataBinding');
    expect(out).toContain('w:storeItemID="{1B2C3D4E-0000-0000-0000-000000000001}"');
    expect(out).toContain('w15:repeatingSection');
    expect(out).toContain('w:val="sdtContentLocked"');
    expect(out).toContain('w:lastValue="2"');
    expect(out).toContain('w:displayText="Archived"');
    expect(out).toContain('w:sdtEndPr');
  });

  test('a w15:repeatingSection block control parses as a control, not flattened (#622 eval)', async () => {
    // The exact case an evaluator reported as broken on the published 1.x: a
    // block-level SDT carrying w15:repeatingSection was flattened to plain
    // paragraphs. It must now survive as a blockSdt whose own sdtPr keeps the
    // (unmodeled) repeatingSection element verbatim.
    const body = parseDocumentBody(await documentXml());
    const repeat = body.content.find(
      (b): b is BlockSdt => b.type === 'blockSdt' && b.properties.tag === 'repeat'
    );
    expect(repeat).toBeDefined();
    expect(repeat!.content.length).toBeGreaterThan(0); // wraps real content, not dropped
    expect(repeat!.properties.rawPropertiesXml).toContain('repeatingSection');
  });

  test('Phase 2 edit cycle (parse → PM → back → serialize) keeps controls + raw sdtPr', async () => {
    const body = parseDocumentBody(await documentXml());
    const pm = toProseDoc({ package: { document: body } } as Document);
    const out = serializeDocumentBody(fromProseDoc(pm).package.document);

    expect((out.match(/<w:sdt>/g) ?? []).length).toBe(11);
    // Phase 1's lossless guarantee must hold through the editor round trip.
    expect(out).toContain('w:dataBinding');
    expect(out).toContain('w15:repeatingSection');
    expect(out).toContain('w:val="sdtContentLocked"');
    expect(out).toContain('w:lastValue="2"');

    // Caret-trap fix: a trailing paragraph follows the doc-final control.
    expect(pm.child(pm.childCount - 1).type.name).toBe('paragraph');
  });
});
