import { describe, it, expect } from 'vitest'
// @ts-expect-error — plain JS ESM audit script, no types
import { classifyLines, findSpacingViolations, GROUPING_RATIO } from '../scripts/audit-mockup-bilingual-spacing.mjs'

// helper: build a Figma-REST-shaped TEXT node whose characters are EN/ZH lines.
// Each line is an array of { font, n, lh } runs; runs become characterStyleOverrides.
function textNode(id: string, name: string, lines: Array<Array<{ font: string; n: number; lh: number }>>) {
  let chars = ''
  const overrides: number[] = []
  const table: Record<number, any> = {}
  let styleId = 1
  lines.forEach((runs, li) => {
    if (li > 0) { chars += '\n'; overrides.push(0) } // newline inherits base
    for (const run of runs) {
      const id = styleId++
      table[id] = { fontFamily: run.font, lineHeightPx: run.lh, fontSize: run.font === 'Roboto' ? 14 : 12 }
      for (let k = 0; k < run.n; k++) { chars += run.font === 'Roboto' ? 'a' : '文'; overrides.push(id) }
    }
  })
  return {
    id,
    type: 'TEXT',
    name,
    characters: chars,
    characterStyleOverrides: overrides,
    styleOverrideTable: table,
    style: { fontFamily: 'Roboto', fontSize: 14, lineHeightPx: 20 },
  }
}

function annotationCard(children: any[]) {
  return { id: 'root', type: 'FRAME', name: 'UX · Delivery Card', children }
}

describe('classifyLines — line font/lineHeight classification', () => {
  it('classifies EN-dominant and ZH-dominant lines and picks max lineHeight', () => {
    const node = textNode('t', 'spec', [
      [{ font: 'Roboto', n: 5, lh: 25 }],
      [{ font: 'Noto Sans SC', n: 4, lh: 14 }],
    ])
    const lines = classifyLines(node)
    expect(lines.map((l: any) => l.kind)).toEqual(['en', 'zh'])
    expect(lines[0].lh).toBe(25)
    expect(lines[1].lh).toBe(14)
  })

  it('skips pure-whitespace lines', () => {
    const node = { id: 't', type: 'TEXT', name: 'x', characters: 'abc\n   \n文文', characterStyleOverrides: [], styleOverrideTable: {}, style: { fontFamily: 'Roboto', lineHeightPx: 20 } }
    // base font Roboto → 'abc' EN; '文文' has no override so also base Roboto → en. Whitespace line dropped.
    expect(classifyLines(node).length).toBe(2)
  })
})

describe('findSpacingViolations — §M23.14 machine gate', () => {
  it('FAILS a multi-pair stacked node with uniform line spacing (ratio 1.0)', () => {
    const node = textNode('bad', 'PRD spec', [
      [{ font: 'Roboto', n: 6, lh: 20 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
      [{ font: 'Roboto', n: 6, lh: 20 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
    ])
    const { violations } = findSpacingViolations(annotationCard([node]))
    expect(violations.map((v: any) => v.nodeId)).toEqual(['bad'])
    expect(violations[0].ratio).toBe(1)
  })

  it('PASSES a multi-pair stacked node with M23.14 grouping (EN ~1.8× ZH)', () => {
    const node = textNode('good', 'PRD spec', [
      [{ font: 'Roboto', n: 6, lh: 25 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 14 }],
      [{ font: 'Roboto', n: 6, lh: 25 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 14 }],
    ])
    expect(findSpacingViolations(annotationCard([node])).violations).toEqual([])
  })

  it('FAILS pure-AUTO-like spacing (ratio ≈1.05 below floor)', () => {
    const node = textNode('auto', 'spec', [
      [{ font: 'Roboto', n: 6, lh: 21 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
      [{ font: 'Roboto', n: 6, lh: 21 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
    ])
    expect(findSpacingViolations(annotationCard([node])).violations.length).toBe(1)
  })

  it('does NOT scan single-pair nodes (itemSpacing mechanism) — counted as skipped', () => {
    const node = textNode('single', 'spec', [
      [{ font: 'Roboto', n: 6, lh: 20 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
    ])
    const r = findSpacingViolations(annotationCard([node]))
    expect(r.violations).toEqual([])
    expect(r.skippedSinglePair).toBe(1)
  })

  it('does NOT scan TEXT outside annotation regions (product UI)', () => {
    const node = textNode('prod', 'device-screen', [
      [{ font: 'Roboto', n: 6, lh: 20 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
      [{ font: 'Roboto', n: 6, lh: 20 }],
      [{ font: 'Noto Sans SC', n: 5, lh: 20 }],
    ])
    const product = { id: 'root', type: 'FRAME', name: 'S1 device', children: [node] }
    expect(findSpacingViolations(product).violations).toEqual([])
  })

  it('counts stacked nodes with no resolvable lineHeight as skipped, not failed', () => {
    // EN runs = base Roboto (no lh); ZH runs = override to Noto Sans SC (no lh).
    // No lineHeightPx anywhere → enLH/zhLH null → skipped, not failed.
    const chars = 'aaaaaa\n文文文文\naaaaaa\n文文文文'
    const overrides: number[] = []
    for (const c of chars) overrides.push(c === '文' ? 1 : 0)
    const node = {
      id: 'nolh', type: 'TEXT', name: 'spec',
      characters: chars,
      characterStyleOverrides: overrides,
      styleOverrideTable: { 1: { fontFamily: 'Noto Sans SC', fontSize: 12 } }, // no lineHeightPx
      style: { fontFamily: 'Roboto', fontSize: 14 }, // no lineHeightPx
    }
    const r = findSpacingViolations(annotationCard([node]))
    expect(r.violations).toEqual([])
    expect(r.skippedNoLH).toBe(1)
  })

  it('GROUPING_RATIO floor is 1.3', () => {
    expect(GROUPING_RATIO).toBe(1.3)
  })
})
