// INFRA-F129 ②：满幅覆盖子矩形 → root 视觉合成。
// 判据真源 = scripts/lib/full-bleed-cover.mjs 头注释（含「为什么限到 RECTANGLE」的全库实测）。
import fs from 'node:fs'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import {
  BOX_FILLING_TYPES,
  fullBleedCoverChildren,
  rootFillPaintStack,
} from '../scripts/lib/full-bleed-cover.mjs'
import { compositePaintsForTheme } from '../scripts/lib/composite-paints.mjs'

const solid = (hex: string, extra: Record<string, unknown> = {}) => ({
  type: 'SOLID', visible: true, opacity: 1, blendMode: 'NORMAL', hex, ...extra,
})

/** 40×20 的帧，坐标刻意用非整数（Figma 真实数据就是浮点）。 */
function frame(overrides: Record<string, unknown> = {}) {
  return { x: -2061.6, y: 5610, w: 40, h: 20, fills: [], children: [], ...overrides }
}
function coverRect(overrides: Record<string, unknown> = {}) {
  return {
    id: 'r1', name: 'Rectangle 535', type: 'RECTANGLE', visible: true, opacity: 1,
    x: -2061.6, y: 5610, w: 40, h: 20, fills: [solid('#1a652c')], ...overrides,
  }
}

describe('fullBleedCoverChildren — 命中面', () => {
  it('满幅 RECTANGLE 被认成覆盖层（switch Rectangle 535 形态）', () => {
    const hits = fullBleedCoverChildren(frame({ children: [coverRect()] }))
    expect(hits.map((h) => h.id)).toEqual(['r1'])
  })

  it('容差内的浮点抖动仍算满幅（0.4px < 0.5px）', () => {
    const hits = fullBleedCoverChildren(frame({ children: [coverRect({ x: -2061.2, h: 20.4 })] }))
    expect(hits).toHaveLength(1)
  })

  it('多个覆盖层按 children 顺序全部叠上（children[0] 在底）', () => {
    const hits = fullBleedCoverChildren(frame({
      children: [coverRect({ id: 'bottom' }), coverRect({ id: 'top', fills: [solid('#ffffff')] })],
    }))
    expect(hits.map((h) => h.id)).toEqual(['bottom', 'top'])
  })
})

describe('fullBleedCoverChildren — 阴性对照（每条都是一种会造假阳的形态）', () => {
  it('⛔ VECTOR 的 bbox 等于帧不算覆盖（图标路径留白，bbox 判据=启发式）', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ type: 'VECTOR' })] }))).toEqual([])
  })

  it('⛔ ELLIPSE / BOOLEAN_OPERATION 同样不算', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ type: 'ELLIPSE' })] }))).toEqual([])
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ type: 'BOOLEAN_OPERATION' })] }))).toEqual([])
  })

  it('⛔ 近零 opacity 的矩形不算（icon/output 的点击热区 opacity=0.0001）', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ opacity: 0.0001 })] }))).toEqual([])
  })

  it('⛔ visible:false 的矩形不算', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ visible: false })] }))).toEqual([])
  })

  it('⛔ 差一整像素就不是满幅', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ w: 39 })] }))).toEqual([])
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ y: 5611 })] }))).toEqual([])
  })

  it('⛔ 没有可见 paint 的矩形不算（fills 空 / paint 不可见 / paint opacity=0）', () => {
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ fills: [] })] }))).toEqual([])
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ fills: [solid('#1a652c', { visible: false })] })] }))).toEqual([])
    expect(fullBleedCoverChildren(frame({ children: [coverRect({ fills: [solid('#1a652c', { opacity: 0 })] })] }))).toEqual([])
  })

  it('⛔ 帧自身无 fill 时，半透明覆盖层不合成（底下是未知宿主背景，合不出真值）', () => {
    const semi = coverRect({ fills: [solid('#1a652c', { opacity: 0.5 })] })
    expect(fullBleedCoverChildren(frame({ children: [semi] }))).toEqual([])
    // 帧自己有 fill 时，底就是已知量 ⇒ 可以合成
    const withBase = frame({ fills: [solid('#000000')], children: [semi] })
    expect(fullBleedCoverChildren(withBase)).toHaveLength(1)
  })

  it('⛔ 只看直接子节点：包在 GROUP 里的满幅矩形不算', () => {
    const grouped = {
      id: 'g', name: 'Group', type: 'GROUP', visible: true, opacity: 1,
      x: -2061.6, y: 5610, w: 40, h: 20, fills: [], children: [coverRect()],
    }
    expect(fullBleedCoverChildren(frame({ children: [grouped] }))).toEqual([])
  })

  it('⛔ 帧缺 w/h 时不判（fail closed 到今天的行为）', () => {
    expect(fullBleedCoverChildren({ children: [coverRect()] })).toEqual([])
    expect(fullBleedCoverChildren(null)).toEqual([])
  })
})

describe('rootFillPaintStack', () => {
  it('无覆盖层时逐字返回帧自身 fills（引用等价，零行为变化）', () => {
    const own = [solid('#2fb54e')]
    expect(rootFillPaintStack(frame({ fills: own }))).toBe(own)
  })

  it('有覆盖层时 = 帧 fills（底）+ 覆盖层 fills（顶）', () => {
    const stack = rootFillPaintStack(frame({ fills: [solid('#000000')], children: [coverRect()] }))
    expect(stack.map((p) => (p as { hex: string }).hex)).toEqual(['#000000', '#1a652c'])
  })

  it('合成结果 = 顶层不透明覆盖色（switch enable=no 的真实期望值）', () => {
    const stack = rootFillPaintStack(frame({ children: [coverRect()] }))
    expect(compositePaintsForTheme(stack, 'dark', {})).toBe('#1a652c')
  })
})

describe('真数据钉（figma-data 活源，⛔ 别改成 mock —— 它钉的正是「全库还有多少」那一问）', () => {
  const dir = path.join(process.cwd(), 'figma-data/normalized/components-tokenized')
  const read = (file: string) => JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'))

  it('switch 的 enable=no 变体：凡满幅子节点是 RECTANGLE 的都命中，命中的都是 Rectangle 535', () => {
    const sw = read('switch__348_3483.json')
    const disabled = sw.variants.filter((v: { name: string }) => /enable=no/.test(v.name))
    expect(disabled.length).toBeGreaterThan(0)
    let covered = 0
    for (const variant of disabled) {
      const hasRect = (variant.children ?? []).some((c: { type: string }) => c.type === 'RECTANGLE')
      const hits = fullBleedCoverChildren(variant)
      if (!hasRect) continue
      expect(hits, `${variant.id} ${variant.name}`).toHaveLength(1)
      expect(hits[0].name).toBe('Rectangle 535')
      covered += 1
    }
    expect(covered).toBeGreaterThan(1)
  })

  // ⚠️ 已知残余，**不是** bug：同一族里有一个变体的那块满幅矩形在 Figma 里是**被拍平过的
  // VECTOR**（名字还叫 `Rectangle 535`，box 与帧逐字相同、fill #cccccc）。
  // 拿名字判 = 命名启发式（禁）；拿 type 判 = 它落在 RECTANGLE 之外 ⇒ 本模块按 fail closed
  // 放它继续是 null，并在豁免表里留一行**只覆盖这一个变体**的窄豁免。
  // 要真闭合它，需要 extract 额外烘出 `fillGeometry` 路径、再机械证明路径填满边界盒 ——
  // 那是独立一件事（当前 tokenized 数据里没有任何几何字段可用）。
  it('已知残余：337:18086 的满幅块是拍平成 VECTOR 的矩形 ⇒ 仍不合成（fail closed）', () => {
    const sw = read('switch__348_3483.json')
    const variant = sw.variants.find((v: { id: string }) => v.id === '337:18086')
    expect(variant).toBeTruthy()
    const flattened = variant.children.find((c: { name: string }) => c.name === 'Rectangle 535')
    expect(flattened.type).toBe('VECTOR')
    expect(fullBleedCoverChildren(variant)).toEqual([])
  })

  it('阳性对照：337:18075（enable=yes, status=on）帧自身有 fill、子节点只有 ellipse ⇒ 0 覆盖层', () => {
    const sw = read('switch__348_3483.json')
    const variant = sw.variants.find((v: { id: string }) => v.id === '337:18075')
    expect(variant).toBeTruthy()
    expect(fullBleedCoverChildren(variant)).toEqual([])
    expect(compositePaintsForTheme(rootFillPaintStack(variant), 'dark', {})).toBe('#2fb54e')
  })

  it('icon/output 的 opacity=0.0001 热区矩形不被算成覆盖层', () => {
    const icon = read('icon_output_kuaishou_1__1132_3161.json')
    const variant = icon.variants[0]
    const rect = (variant.children ?? []).find((c: { type: string }) => c.type === 'RECTANGLE')
    expect(rect).toBeTruthy()
    expect(rect.opacity).toBeLessThan(0.01)
    expect(fullBleedCoverChildren(variant)).toEqual([])
  })

  it('全库扫一遍：被认成覆盖层的节点类型只能是 BOX_FILLING_TYPES 里的', () => {
    const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'))
    const types = new Set<string>()
    for (const file of files) {
      for (const variant of read(file).variants ?? []) {
        for (const hit of fullBleedCoverChildren(variant)) types.add(hit.type)
      }
    }
    expect(types.size).toBeGreaterThan(0)
    for (const type of types) expect(BOX_FILLING_TYPES.has(type)).toBe(true)
  })
})
