import { describe, it, expect } from 'vitest'
import {
  // @ts-expect-error — plain JS ESM audit script, no types
  findRhythmBreaks, groupDividers, pitches, PITCH_TOL, CENTER_TOL, MIN_DIVIDERS,
} from '../scripts/audit-mockup-row-rhythm.mjs'

// 几何取自真实交付帧 `0054ib0nLmt27bC3QlGDl7` / `8062:8004`（LCD 选 R 页，2026-09-16 实测）。
// 原稿分隔线 y = 677.9 / 713.9 / 749.0 / 785.0 / 820.1 ⇒ 间距 36.02 / 35.07 / 36.02 / 35.07，
// 而标签按等距 37 排 ⇒ 逐行漂移。owner 逐字：「分割线不均匀，这个是视觉上的问题，
// 在设计上时是大忌」。
const DIVIDER_YS = [677.9, 713.92, 749.0, 785.0, 820.07]
const divider = (i: number, y: number) => ({
  id: `d${i}`, name: `Rectangle 2 Copy ${i}`, type: 'RECTANGLE',
  fills: [{ type: 'SOLID', color: { r: 0.145, g: 0.145, b: 0.145 } }],
  absoluteBoundingBox: { x: 745, y, width: 480, height: 0.95 },
})
const label = (id: string, characters: string, y: number) => ({
  id, name: characters, type: 'TEXT', characters,
  fills: [{ type: 'SOLID', color: { r: 1, g: 1, b: 1 } }],
  absoluteBoundingBox: { x: 1081, y, width: 79, height: 19 },
})
const content = (children: any[]) => ({ id: 'c', name: 'Content', type: 'FRAME', children })

const unevenDividers = DIVIDER_YS.map((y, i) => divider(i, y))
// 等距版：40px pitch（owner 2026-09-16 选定），首条对齐原位
const evenDividers = [0, 1, 2, 3, 4].map((i) => divider(i, 677.9 + i * 40))

describe('groupDividers — 谁算一组分隔线', () => {
  it('must-hit：同 x 同宽的 5 条细矩形归为一组', () => {
    expect(groupDividers(unevenDividers)).toHaveLength(1)
    expect(groupDividers(unevenDividers)[0]).toHaveLength(5)
  })

  it(`🔴 阴性对照：少于 ${MIN_DIVIDERS} 条 ⛔ 不成组（无「节奏」可言）`, () => {
    expect(groupDividers(unevenDividers.slice(0, 2))).toHaveLength(0)
  })

  it('🔴 阴性对照：宽度不同的细线属不同视觉系统 ⇒ 按宽度分开成组，⛔ 不混为一组', () => {
    // 6 条：3 条 480 宽 + 3 条 240 宽，两组各自够数 ⇒ 应得 2 组，⛔ 不是 1 组 6 条
    const wide = [0, 1, 2].map((i) => divider(i, 677.9 + i * 40))
    const narrow = [3, 4, 5].map((i) => ({
      ...divider(i, 690 + (i - 3) * 40),
      absoluteBoundingBox: { x: 745, y: 690 + (i - 3) * 40, width: 240, height: 0.95 },
    }))
    const groups = groupDividers([...wide, ...narrow])
    expect(groups).toHaveLength(2)
    expect(groups.map((g: any[]) => g.length).sort()).toEqual([3, 3])
  })

  it('🔴 阴性对照：两组各自不足数时 ⇒ 0 组（不足数的组不参与判定）', () => {
    const mixed = [
      divider(0, 677.9), divider(1, 717.9),
      { ...divider(2, 690), absoluteBoundingBox: { x: 745, y: 690, width: 240, height: 0.95 } },
      { ...divider(3, 730), absoluteBoundingBox: { x: 745, y: 730, width: 240, height: 0.95 } },
    ]
    expect(groupDividers(mixed)).toHaveLength(0)
  })

  it('🔴 阴性对照：高度超过阈值的矩形是内容块 ⛔ 不是分隔线', () => {
    const thick = unevenDividers.map((d) => ({
      ...d, absoluteBoundingBox: { ...d.absoluteBoundingBox, height: 33 },
    }))
    expect(groupDividers(thick)).toHaveLength(0)
  })

  it('🔴 阴性对照：没有可见填充的细矩形 ⛔ 不算分隔线', () => {
    const invisible = unevenDividers.map((d) => ({ ...d, fills: [{ type: 'SOLID', visible: false }] }))
    expect(groupDividers(invisible)).toHaveLength(0)
  })
})

describe('D1 — 分隔线等距', () => {
  it('🔴 must-hit：原稿间距 36.02/35.07/36.02/35.07 ⇒ 极差 0.95 > 容差，报红', () => {
    const { findings } = findRhythmBreaks(content(unevenDividers))
    const d1 = findings.filter((f: any) => f.probe === 'D1')
    expect(d1).toHaveLength(1)
    expect(d1[0].spread).toBeCloseTo(0.95, 2)
    expect(d1[0].pitches).toEqual([36.02, 35.08, 36, 35.07])
  })

  it('✅ 修好后：40px 等距 ⇒ 0 findings', () => {
    const { findings } = findRhythmBreaks(content(evenDividers))
    expect(findings.filter((f: any) => f.probe === 'D1')).toHaveLength(0)
  })

  it(`容差边界：极差恰为 ${PITCH_TOL} ⇒ 不报（> 才报）`, () => {
    const edge = [0, 1, 2].map((i) => divider(i, 677.9 + i * 40))
    edge[2].absoluteBoundingBox.y += PITCH_TOL
    expect(findRhythmBreaks(content(edge)).findings.filter((f: any) => f.probe === 'D1')).toHaveLength(0)
  })
})

describe('D2 — 标签在行带内垂直居中（判据 = 上留白 == 下留白）', () => {
  it('🔴 must-hit：原稿标签按等距 37 排，落在不均行带里 ⇒ 逐个报红', () => {
    const labels = [
      label('t1', 'XMM_X8L', 684), label('t2', 'YLA_0912', 721),
      label('t3', 'PM_X7M', 759), label('t4', 'PM_UED', 796),
    ]
    const d2 = findRhythmBreaks(content([...unevenDividers, ...labels]))
      .findings.filter((f: any) => f.probe === 'D2')
    expect(d2.length).toBeGreaterThanOrEqual(3)
    const xmm = d2.find((f: any) => f.label === 'XMM_X8L')
    expect(xmm.gapTop).toBeCloseTo(5.14, 1)
    expect(xmm.gapBottom).toBeCloseTo(10.93, 1)
    expect(xmm.skew).toBeCloseTo(5.79, 1)
  })

  it('✅ 修好后：标签按行带中线摆放 ⇒ 上下留白相等，0 findings', () => {
    // 行带 = [677.9+0.95, 717.9]，高 39.05；标签高 19 ⇒ 上下各 10.03
    const centred = [1, 2, 3].map((i) =>
      label(`t${i}`, `L${i}`, 677.9 + 0.95 + (i - 1) * 40 + (39.05 - 19) / 2))
    const d2 = findRhythmBreaks(content([...evenDividers, ...centred]))
      .findings.filter((f: any) => f.probe === 'D2')
    expect(d2).toHaveLength(0)
  })

  it(`🔴 阴性对照：偏差恰为容差 ${CENTER_TOL}px ⇒ 不报（留给字形与 TEXT 框的固有差）`, () => {
    const band0 = 677.9 + 0.95
    const centredY = band0 + (39.05 - 19) / 2
    const skewed = [label('t1', 'L1', centredY + CENTER_TOL / 2)]
    const d2 = findRhythmBreaks(content([...evenDividers, ...skewed]))
      .findings.filter((f: any) => f.probe === 'D2')
    expect(d2).toHaveLength(0)
  })

  it('🔴 阴性对照：跨越行带边界的文字 ⛔ 不判（它不属于任何单一行）', () => {
    const tall = { ...label('t1', 'Tall', 700), absoluteBoundingBox: { x: 1081, y: 700, width: 79, height: 60 } }
    const d2 = findRhythmBreaks(content([...evenDividers, tall]))
      .findings.filter((f: any) => f.probe === 'D2')
    expect(d2).toHaveLength(0)
  })
})

describe('分母诚实', () => {
  it('没有分隔线组时 checkedDividerGroups = 0（调用方须标未验，⛔ 不是通过）', () => {
    const r = findRhythmBreaks(content([label('t1', 'solo', 100)]))
    expect(r.checkedDividerGroups).toBe(0)
    expect(r.checkedLabels).toBe(0)
  })

  it('有分隔线但无标签落在带内 ⇒ D1 有分母、D2 分母为 0', () => {
    const r = findRhythmBreaks(content(evenDividers))
    expect(r.checkedDividerGroups).toBe(1)
    expect(r.checkedLabels).toBe(0)
  })
})
