import { afterEach, describe, expect } from 'vitest';
import { join } from 'node:path';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import AdmZip from 'adm-zip';
import { validateTemplate } from '../src/core/validation/template.js';
import { validateMetadata } from '../src/core/metadata.js';
import { prepareFillData } from '../src/core/fill-pipeline.js';
import {
  allureAttachment,
  allureJsonAttachment,
  allureParameter,
  allureStep,
  itAllure,
} from './helpers/allure-test.js';

// These tests rely on the existing templates in the repo
const templatesDir = join(import.meta.dirname, '..', 'content', 'templates');
const tempDirs: string[] = [];
const it = itAllure.epic('Verification & Drift');

afterEach(() => {
  for (const dir of tempDirs.splice(0)) {
    rmSync(dir, { recursive: true, force: true });
  }
});

const CONTENT_TYPES_XML =
  '<?xml version="1.0" encoding="UTF-8"?>' +
  '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
  '<Default Extension="xml" ContentType="application/xml"/>' +
  '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
  '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>' +
  '</Types>';

const RELS_XML =
  '<?xml version="1.0" encoding="UTF-8"?>' +
  '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
  '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>' +
  '</Relationships>';

const WORD_RELS_XML =
  '<?xml version="1.0" encoding="UTF-8"?>' +
  '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"/>';

const W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';

function buildDocx(documentText: string): Buffer {
  const zip = new AdmZip();
  const xml =
    '<?xml version="1.0" encoding="UTF-8"?>' +
    `<w:document xmlns:w="${W_NS}"><w:body><w:p><w:r><w:t>${documentText}</w:t></w:r></w:p></w:body></w:document>`;
  zip.addFile('[Content_Types].xml', Buffer.from(CONTENT_TYPES_XML, 'utf-8'));
  zip.addFile('_rels/.rels', Buffer.from(RELS_XML, 'utf-8'));
  zip.addFile('word/_rels/document.xml.rels', Buffer.from(WORD_RELS_XML, 'utf-8'));
  zip.addFile('word/document.xml', Buffer.from(xml, 'utf-8'));
  return zip.toBuffer();
}

function createTemplateFixture(opts: {
  docText: string;
  fieldsYaml: string;
  priorityFields?: string[];
  replacements?: Record<string, string>;
  replacementsRaw?: string;
  writeTemplateDocx?: boolean;
}): string {
  const dir = mkdtempSync(join(tmpdir(), 'oa-template-validate-'));
  tempDirs.push(dir);
  const priorityFields = opts.priorityFields ?? [];
  const priorityFieldsLines =
    priorityFields.length === 0
      ? ['priority_fields: []']
      : ['priority_fields:', ...priorityFields.map((field) => `  - ${field}`)];
  writeFileSync(
    join(dir, 'metadata.yaml'),
    [
      'name: Fixture Template',
      'source_url: https://example.com/template.docx',
      'version: "1.0"',
      'license: CC-BY-4.0',
      'allow_derivatives: true',
      'attribution_text: Example Attribution',
      'fields:',
      opts.fieldsYaml,
      ...priorityFieldsLines,
      '',
    ].join('\n'),
    'utf-8'
  );
  if (opts.writeTemplateDocx ?? true) {
    writeFileSync(join(dir, 'template.docx'), buildDocx(opts.docText));
  }
  if (opts.replacementsRaw !== undefined) {
    writeFileSync(join(dir, 'replacements.json'), opts.replacementsRaw, 'utf-8');
  } else if (opts.replacements) {
    writeFileSync(join(dir, 'replacements.json'), JSON.stringify(opts.replacements, null, 2), 'utf-8');
  }
  return dir;
}

function buildDocxWithoutDocumentXml(): Buffer {
  const zip = new AdmZip();
  zip.addFile('[Content_Types].xml', Buffer.from(CONTENT_TYPES_XML, 'utf-8'));
  zip.addFile('_rels/.rels', Buffer.from(RELS_XML, 'utf-8'));
  zip.addFile('word/_rels/document.xml.rels', Buffer.from(WORD_RELS_XML, 'utf-8'));
  return zip.toBuffer();
}

describe('validateTemplate', () => {
  it.openspec('OA-TMP-015')('validates bonterms-mutual-nda without errors', async () => {
    const dir = join(templatesDir, 'bonterms-mutual-nda');
    await allureParameter('template_id', 'bonterms-mutual-nda');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'bonterms-mutual-nda')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });

  it.openspec('OA-TMP-015')('validates common-paper-mutual-nda without errors', async () => {
    const dir = join(templatesDir, 'common-paper-mutual-nda');
    await allureParameter('template_id', 'common-paper-mutual-nda');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'common-paper-mutual-nda')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });

  it.openspec('OA-TMP-015')('validates openagreements-employment-offer-letter without errors', async () => {
    const dir = join(templatesDir, 'openagreements-employment-offer-letter');
    await allureParameter('template_id', 'openagreements-employment-offer-letter');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'openagreements-employment-offer-letter')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });

  it.openspec('OA-TMP-015')('validates openagreements-employee-ip-inventions-assignment without errors', async () => {
    const dir = join(templatesDir, 'openagreements-employee-ip-inventions-assignment');
    await allureParameter('template_id', 'openagreements-employee-ip-inventions-assignment');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'openagreements-employee-ip-inventions-assignment')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });

  it.openspec('OA-TMP-015')('validates openagreements-employment-confidentiality-acknowledgement without errors', async () => {
    const dir = join(templatesDir, 'openagreements-employment-confidentiality-acknowledgement');
    await allureParameter('template_id', 'openagreements-employment-confidentiality-acknowledgement');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'openagreements-employment-confidentiality-acknowledgement')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });

  it.openspec('OA-TMP-015')('validates openagreements-restrictive-covenant-wyoming without errors', async () => {
    const dir = join(templatesDir, 'openagreements-restrictive-covenant-wyoming');
    await allureParameter('template_id', 'openagreements-restrictive-covenant-wyoming');
    const result = await allureStep('Validate template', () =>
      validateTemplate(dir, 'openagreements-restrictive-covenant-wyoming')
    );
    await allureJsonAttachment('template-validation-result.json', result);

    await allureStep('Assert template passes validation', () => {
      expect(result.valid).toBe(true);
      expect(result.errors).toEqual([]);
    });
  });
});

describe('validateMetadata', () => {
  it.openspec('OA-TMP-015')('validates bonterms-mutual-nda metadata', async () => {
    const dir = join(templatesDir, 'bonterms-mutual-nda');
    await allureParameter('template_id', 'bonterms-mutual-nda');
    const result = await allureStep('Validate metadata.yaml', () => validateMetadata(dir));
    await allureJsonAttachment('metadata-validation-result.json', result);

    await allureStep('Assert metadata validation passes', () => {
      expect(result.valid).toBe(true);
    });
  });

  it.openspec('OA-TMP-015')('validates employment offer metadata', async () => {
    const dir = join(templatesDir, 'openagreements-employment-offer-letter');
    await allureParameter('template_id', 'openagreements-employment-offer-letter');
    const result = await allureStep('Validate metadata.yaml', () => validateMetadata(dir));
    await allureJsonAttachment('metadata-validation-result.json', result);

    await allureStep('Assert metadata validation passes', () => {
      expect(result.valid).toBe(true);
    });
  });
});

describe('validateTemplate placeholder coverage', () => {
  it.openspec('OA-TMP-001')('reports error for missing required placeholder', async () => {
    const dir = createTemplateFixture({
      docText: 'No placeholders here',
      fieldsYaml: '  - name: required_field\n    type: string\n    description: Required',
      priorityFields: ['required_field'],
    });
    await allureParameter('fixture_template_id', 'fixture-required-missing');
    await allureAttachment('fixture-doc-text.txt', 'No placeholders here');
    const result = await allureStep('Validate required placeholder coverage', () =>
      validateTemplate(dir, 'fixture-required-missing')
    );
    await allureJsonAttachment('required-placeholder-result.json', result);

    await allureStep('Assert required placeholder error is reported', () => {
      expect(result.valid).toBe(false);
      expect(result.errors.join(' ')).toContain('Priority field \"required_field\"');
    });
  });

  it.openspec('OA-TMP-002')('reports warning for missing optional placeholder', async () => {
    const dir = createTemplateFixture({
      docText: 'No placeholders here either',
      fieldsYaml: '  - name: optional_field\n    type: string\n    description: Optional',
      priorityFields: [],
    });
    await allureParameter('fixture_template_id', 'fixture-optional-missing');
    await allureAttachment('fixture-doc-text.txt', 'No placeholders here either');
    const result = await allureStep('Validate optional placeholder coverage', () =>
      validateTemplate(dir, 'fixture-optional-missing')
    );
    await allureJsonAttachment('optional-placeholder-result.json', result);

    await allureStep('Assert optional placeholder warning is reported', () => {
      expect(result.valid).toBe(true);
      expect(result.warnings.join(' ')).toContain('Optional field \"optional_field\"');
    });
  });

  it.openspec('OA-TMP-016')('reports required-field errors for declarative replacements missing metadata tags', async () => {
    const dir = createTemplateFixture({
      docText: 'Order form has [Company Name] only',
      fieldsYaml: '  - name: required_field\n    type: string\n    description: Priority field',
      priorityFields: ['required_field'],
      replacements: {
        '[Company Name]': '{other_field}',
      },
    });

    const result = validateTemplate(dir, 'fixture-required-missing-in-replacements');
    await allureJsonAttachment('required-missing-in-replacements-result.json', result);
    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Priority field \"required_field\" defined in metadata but not found');
  });

  it('returns an error when metadata cannot be loaded', () => {
    const dir = mkdtempSync(join(tmpdir(), 'oa-template-metadata-missing-'));
    tempDirs.push(dir);

    const result = validateTemplate(dir, 'fixture-missing-metadata');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Failed to load metadata');
  });

  it('returns an error when template.docx is absent', () => {
    const dir = createTemplateFixture({
      docText: 'Unused',
      fieldsYaml: '  - name: company_name\n    type: string\n    description: Company name',
      writeTemplateDocx: false,
    });

    const result = validateTemplate(dir, 'fixture-missing-docx');

    expect(result.valid).toBe(false);
    expect(result.errors).toContain('template.docx not found in template directory');
  });

  it('returns an error when replacements.json cannot be parsed', () => {
    const dir = createTemplateFixture({
      docText: '[Company Name]',
      fieldsYaml: '  - name: company_name\n    type: string\n    description: Company name',
      replacementsRaw: '{invalid-json',
    });

    const result = validateTemplate(dir, 'fixture-invalid-replacements-json');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Failed to parse replacements.json');
  });

  it('validates declarative replacement keys and placeholder coverage', () => {
    const dir = createTemplateFixture({
      docText: '[Company Name] {doc_only} {IF doc_flag}{END-IF} {IF}',
      fieldsYaml: [
        '  - name: company_name',
        '    type: string',
        '    description: Company name',
        '  - name: unused_optional',
        '    type: string',
        '    description: Optional but unused',
      ].join('\n'),
      priorityFields: ['company_name'],
      replacements: {
        '[Missing Placeholder]': '{company_name}',
        '[Company Name]': '{company_name} {IF company_name}{END-IF}',
      },
    });

    const result = validateTemplate(dir, 'fixture-declarative-coverage');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Replacement key "[Missing Placeholder]" not found');
    expect(result.warnings.join(' ')).toContain('Optional field "unused_optional"');
    expect(result.warnings.join(' ')).toContain('Placeholder {doc_only} found in template but not defined in metadata fields');
  });

  it('accepts FOR loop placeholders in direct template scanning mode', () => {
    const dir = createTemplateFixture({
      docText: '{FOR row IN board_members}{$row.name}{END-FOR row}',
      fieldsYaml: [
        '  - name: board_members',
        '    type: array',
        '    description: Board members',
        '    items:',
        '      - name: name',
        '        type: string',
        '        description: Member name',
      ].join('\n'),
    });

    const result = validateTemplate(dir, 'fixture-for-loop');

    expect(result.valid).toBe(true);
    expect(result.errors).toEqual([]);
  });

  it('rejects {FOR ... IN field} when field is not declared array', () => {
    const dir = createTemplateFixture({
      docText: '{FOR row IN board_members}{$row.name}{END-FOR row}',
      fieldsYaml: '  - name: board_members\n    type: string\n    description: Board members',
    });

    const result = validateTemplate(dir, 'fixture-for-loop-bad-type');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('requires "board_members" to be type=array');
  });

  it('rejects {FOR ... IN field} when field is missing from metadata', () => {
    const dir = createTemplateFixture({
      docText: '{FOR row IN signatories}{$row.name}{END-FOR row}',
      fieldsYaml: '  - name: company_name\n    type: string\n    description: Company name',
    });

    const result = validateTemplate(dir, 'fixture-for-loop-missing-field');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('not defined in metadata fields');
  });

  it('rejects {$item.field} when row field is not declared in items', () => {
    const dir = createTemplateFixture({
      docText: '{FOR row IN board_members}{$row.title}{END-FOR row}',
      fieldsYaml: [
        '  - name: board_members',
        '    type: array',
        '    description: Board members',
        '    items:',
        '      - name: name',
        '        type: string',
        '        description: Member name',
      ].join('\n'),
    });

    const result = validateTemplate(dir, 'fixture-item-ref-bad');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('"board_members.items" does not declare it');
  });

  it('flags unsafe template control tags in template.docx', () => {
    const dir = createTemplateFixture({
      docText: '{#if hacked}Text{/if}',
      fieldsYaml: '  - name: company_name\n    type: string\n    description: Company name',
    });

    const result = validateTemplate(dir, 'fixture-unsafe-tags');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Unsafe template tag');
  });

  it('warns on unknown placeholders and ignores END control token warnings', () => {
    const dir = createTemplateFixture({
      docText: '{END} {unknown_field}',
      fieldsYaml: '  - name: known_field\n    type: string\n    description: Known field',
    });

    const result = validateTemplate(dir, 'fixture-unknown-placeholder');

    expect(result.valid).toBe(true);
    expect(result.warnings.join(' ')).toContain('Placeholder {unknown_field} found in template.docx');
    expect(result.warnings.join(' ')).not.toContain('Placeholder {END}');
  });

  it('handles DOCX files that do not contain word/document.xml', () => {
    const dir = mkdtempSync(join(tmpdir(), 'oa-template-missing-document-xml-'));
    tempDirs.push(dir);
    writeFileSync(
      join(dir, 'metadata.yaml'),
      [
        'name: Missing XML Fixture',
        'source_url: https://example.com/template.docx',
        'version: "1.0"',
        'license: CC-BY-4.0',
        'allow_derivatives: true',
        'attribution_text: Example attribution',
        'fields:',
        '  - name: optional_field',
        '    type: string',
        '    description: Optional field',
        'priority_fields: []',
        '',
      ].join('\n'),
      'utf-8'
    );
    writeFileSync(join(dir, 'template.docx'), buildDocxWithoutDocumentXml());

    const result = validateTemplate(dir, 'fixture-no-document-xml');

    expect(result.valid).toBe(true);
    expect(result.warnings.join(' ')).toContain('Optional field "optional_field"');
  });
});

describe('validateTemplate multiselect coverage', () => {
  const multiselectFieldsYaml = [
    '  - name: industry_modules',
    '    type: multiselect',
    '    description: Industry riders',
    '    options:',
    '      - tech_rider',
    '      - cross_border_rider',
    '    derive_booleans: true',
  ].join('\n');

  const multiselectWithoutDerivationYaml = [
    '  - name: industry_modules',
    '    type: multiselect',
    '    description: Industry riders',
    '    options:',
    '      - tech_rider',
    '      - cross_border_rider',
  ].join('\n');

  it.openspec('OA-TMP-052')('does not warn about multiselect fields when direct DOCX conditionals use only derived keys', () => {
    const dir = createTemplateFixture({
      docText: '{IF tech_rider_enabled}{END-IF}',
      fieldsYaml: multiselectFieldsYaml,
    });

    const result = validateTemplate(dir, 'fixture-direct-derived-multiselect');

    expect(result.valid).toBe(true);
    expect(result.errors).toEqual([]);
    expect(result.warnings.join(' ')).not.toContain('industry_modules');
  });

  it.openspec('OA-TMP-052')('does not warn about multiselect fields when replacement values use only derived keys', () => {
    const dir = createTemplateFixture({
      docText: '[Industry riders]',
      fieldsYaml: multiselectFieldsYaml,
      replacements: {
        '[Industry riders]': '{IF tech_rider_enabled}{END-IF}',
      },
    });

    const result = validateTemplate(dir, 'fixture-replacements-derived-multiselect');

    expect(result.valid).toBe(true);
    expect(result.errors).toEqual([]);
    expect(result.warnings.join(' ')).not.toContain('industry_modules');
  });

  it.openspec('OA-TMP-052')('rejects direct multiselect IF references in direct DOCX scanning mode', () => {
    const dir = createTemplateFixture({
      docText: '{IF industry_modules}{END-IF}',
      fieldsYaml: multiselectFieldsYaml,
    });

    const result = validateTemplate(dir, 'fixture-direct-if-multiselect');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('must not be referenced directly in {IF industry_modules}');
  });

  it.openspec('OA-TMP-052')('rejects direct multiselect IF references in replacements mode even without derive_booleans', () => {
    const dir = createTemplateFixture({
      docText: '[Industry riders]',
      fieldsYaml: multiselectWithoutDerivationYaml,
      replacements: {
        '[Industry riders]': '{IF industry_modules}{END-IF}',
      },
    });

    const result = validateTemplate(dir, 'fixture-replacements-if-multiselect');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('must not be referenced directly in {IF industry_modules}');
  });

  it.openspec('OA-FIL-026')('throws a clear error for malformed multiselect JSON input', () => {
    expect(() =>
      prepareFillData({
        values: { industry_modules: 'not-json' },
        fields: [
          {
            name: 'industry_modules',
            type: 'multiselect',
            description: 'Industry riders',
            options: ['tech_rider', 'cross_border_rider'],
            derive_booleans: true,
          },
        ],
      })
    ).toThrow('Multiselect field "industry_modules" received malformed JSON input');
  });

  it('treats empty priority multiselect collections as unfilled after normalization', () => {
    expect(() =>
      prepareFillData({
        values: { industry_modules: '[]' },
        fields: [
          {
            name: 'industry_modules',
            type: 'multiselect',
            description: 'Industry riders',
            options: ['tech_rider', 'cross_border_rider'],
            derive_booleans: true,
          },
        ],
        priorityFieldNames: ['industry_modules'],
      })
    ).toThrow('Required collection fields are empty: industry_modules');
  });

  it.openspec('OA-TMP-053')('warns when a derive_booleans multiselect is declared but no derived key is referenced (direct DOCX)', () => {
    const dir = createTemplateFixture({
      docText: 'Body text without any rider conditional.',
      fieldsYaml: multiselectFieldsYaml,
    });

    const result = validateTemplate(dir, 'fixture-derive-booleans-unused-direct');

    expect(result.valid).toBe(true);
    expect(result.warnings.join(' ')).toContain('Optional field "industry_modules"');
  });

  it.openspec('OA-TMP-053')('errors when a priority derive_booleans multiselect is declared but no derived key is referenced (direct DOCX)', () => {
    const dir = createTemplateFixture({
      docText: 'Body text without any rider conditional.',
      fieldsYaml: multiselectFieldsYaml,
      priorityFields: ['industry_modules'],
    });

    const result = validateTemplate(dir, 'fixture-derive-booleans-unused-direct-priority');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Priority field "industry_modules"');
  });

  it.openspec('OA-TMP-053')('warns when a derive_booleans multiselect is declared but no derived key is referenced (replacements mode)', () => {
    const dir = createTemplateFixture({
      docText: '[Industry riders]',
      fieldsYaml: multiselectFieldsYaml,
      replacements: {
        '[Industry riders]': 'plain replacement text without any rider conditional',
      },
    });

    const result = validateTemplate(dir, 'fixture-derive-booleans-unused-replacements');

    expect(result.valid).toBe(true);
    expect(result.warnings.join(' ')).toContain('Optional field "industry_modules"');
  });

  it.openspec('OA-TMP-053')('errors when a priority derive_booleans multiselect is declared but no derived key is referenced (replacements mode)', () => {
    const dir = createTemplateFixture({
      docText: '[Industry riders]',
      fieldsYaml: multiselectFieldsYaml,
      priorityFields: ['industry_modules'],
      replacements: {
        '[Industry riders]': 'plain replacement text without any rider conditional',
      },
    });

    const result = validateTemplate(dir, 'fixture-derive-booleans-unused-replacements-priority');

    expect(result.valid).toBe(false);
    expect(result.errors.join(' ')).toContain('Priority field "industry_modules"');
  });

  it.openspec('OA-FIL-028')('rejects multiselect runtime input with non-string entries', () => {
    expect(() =>
      prepareFillData({
        values: { industry_modules: ['tech_rider', 7] as unknown[] },
        fields: [
          {
            name: 'industry_modules',
            type: 'multiselect',
            description: 'Industry riders',
            options: ['tech_rider', 'cross_border_rider'],
            derive_booleans: true,
          },
        ],
      })
    ).toThrow('entry at index 1 must be a string');
  });

  it.openspec('OA-FIL-028')('rejects multiselect runtime input with unknown options', () => {
    expect(() =>
      prepareFillData({
        values: { industry_modules: ['tech_rider', 'unknown_option'] },
        fields: [
          {
            name: 'industry_modules',
            type: 'multiselect',
            description: 'Industry riders',
            options: ['tech_rider', 'cross_border_rider'],
            derive_booleans: true,
          },
        ],
      })
    ).toThrow('received unknown option "unknown_option"');
  });

  it.openspec('OA-TMP-073')('tolerates the derived {IF any_confirmation_pending} cover-notice tag without an unknown-placeholder finding', () => {
    // The Florida template's regenerated template.docx carries the cover-page
    // confirmation notice gated on {IF any_confirmation_pending} (a derived
    // synthetic key, not a metadata field). Validation must not flag it.
    const flDir = join(templatesDir, 'openagreements-restrictive-covenant-florida');
    const result = validateTemplate(flDir, 'openagreements-restrictive-covenant-florida');
    const mentions = [...result.errors, ...result.warnings].filter((m) =>
      m.includes('any_confirmation_pending')
    );
    expect(mentions).toEqual([]);
    expect(result.errors).toEqual([]);
    // Sanity: the in-body statutory-compliance bracket still validates (no SCR error).
    expect(result.errors.filter((e) => e.includes('statutory_compliance_representation'))).toEqual([]);
  });

  it.openspec('OA-TMP-075')('emits the cover-notice cross-reference anchor, sentinel, and external hyperlink in the unfilled Florida template', () => {
    const flDir = join(templatesDir, 'openagreements-restrictive-covenant-florida');
    const zip = new AdmZip(join(flDir, 'template.docx'));
    const docXml = zip.getEntry('word/document.xml')!.getData().toString('utf-8');
    const relsXml = zip.getEntry('word/_rels/document.xml.rels')!.getData().toString('utf-8');

    // The CHOICE Act counsel clause heading carries an xref bookmark…
    expect(docXml).toMatch(/<w:bookmarkStart[^>]*w:name="oa_xref_[0-9a-f]+"/);
    // …the cover bullet carries the matching (XML-escaped) cross-reference sentinel,
    // inside an internal hyperlink to that bookmark…
    const bm = docXml.match(/w:name="(oa_xref_[0-9a-f]+)"/)![1];
    expect(docXml).toContain(`&lt;&lt;xref:${bm}&gt;&gt;`);
    expect(docXml).toMatch(new RegExp(`<w:hyperlink[^>]*w:anchor="${bm}"`));
    // …and the "for more details" URL is a real external hyperlink relationship.
    expect(relsXml).toMatch(
      /Type="[^"]*\/hyperlink"[^>]*Target="https:\/\/openagreements\.org\/legal\/non-compete\/florida"[^>]*TargetMode="External"/
    );

    // The unresolved sentinel must never be mistaken for an unknown placeholder.
    const result = validateTemplate(flDir, 'openagreements-restrictive-covenant-florida');
    expect([...result.errors, ...result.warnings].filter((m) => m.includes('xref'))).toEqual([]);
    expect(result.errors).toEqual([]);
  });
});
