/**
 * INFRA-F41 Task 2 — manifest emits per-child expectedNodes
 *
 * Verifies that after buildExpectedNodes is added to the manifest generator,
 * every entry for SelectBoxFilled (multi-select) carries an `expectedNodes`
 * array with per-child Figma node records, each containing:
 *   figmaNodeId, name, type, fillHex, borderHex, textColorHex, radius, opacity
 *
 * Specific pin-point assertions:
 *   - chip-close INSTANCE 1436:36578  → fillHex === '#9e9e9e'
 *   - chip frame  FRAME  1436:36576   → fillHex === '#f0f0f0'
 *
 * Initially FAILS (expectedNodes field does not exist). Passes after Task 2
 * implementation adds buildExpectedNodes + attaches it to each entry.
 */

import { execSync } from 'child_process'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import { describe, expect, it, beforeAll } from 'vitest'

const ROOT = resolve(__dirname, '..')
const MANIFEST_PATH = resolve(ROOT, 'figma-data/render-verification-manifest.json')

type ExpectedNode = {
  figmaNodeId: string
  name: string
  type: string
  fillHex: string | null
  borderHex: string | null
  textColorHex: string | null
  radius: number | null
  opacity: number
}

type ManifestEntry = {
  manifestId: string
  figmaName: string
  codeComponent: string
  theme: string
  figmaVariantName: string
  expectedNodes?: ExpectedNode[]
  [key: string]: unknown
}

describe('manifest expectedNodes (INFRA-F41 Task 2)', () => {
  let manifest: ManifestEntry[]

  beforeAll(() => {
    // Regenerate the manifest fresh so we're testing the current generator output.
    execSync('node scripts/generate-render-verification-manifest.mjs', {
      cwd: ROOT,
      stdio: 'pipe',
    })
    const raw = readFileSync(MANIFEST_PATH, 'utf8')
    manifest = JSON.parse(raw)
  })

  it('manifest regenerated and has entries', () => {
    expect(Array.isArray(manifest)).toBe(true)
    expect(manifest.length).toBeGreaterThan(0)
  })

  it('every entry has an expectedNodes field (array)', () => {
    const missingEntries = manifest.filter((e) => !Array.isArray(e.expectedNodes))
    expect(
      missingEntries.length,
      `${missingEntries.length} entries are missing expectedNodes. First missing: ${missingEntries[0]?.manifestId}`,
    ).toBe(0)
  })

  it('SelectBoxFilled multi-select entries exist in the manifest', () => {
    const selectEntries = manifest.filter(
      (e) =>
        (e.figmaName?.toLowerCase().includes('select') ||
          e.codeComponent?.toLowerCase().includes('select')) &&
        e.figmaVariantName?.toLowerCase().includes('multi'),
    )
    expect(
      selectEntries.length,
      'Expected at least one SelectBoxFilled multi-select entry',
    ).toBeGreaterThan(0)
  })

  it('chip-close INSTANCE 1436:36578 appears in expectedNodes with fillHex #9e9e9e', () => {
    // Find any entry whose expectedNodes contains the chip-close icon node
    const entryWithChipClose = manifest.find((e) =>
      Array.isArray(e.expectedNodes) &&
      e.expectedNodes.some((n) => n.figmaNodeId === '1436:36578'),
    )

    expect(
      entryWithChipClose,
      'Expected to find at least one manifest entry whose expectedNodes contains node 1436:36578 (chip-close icon)',
    ).toBeDefined()

    const chipCloseNode = entryWithChipClose!.expectedNodes!.find(
      (n) => n.figmaNodeId === '1436:36578',
    )!

    expect(chipCloseNode.fillHex?.toLowerCase()).toBe('#9e9e9e')
  })

  it('chip frame FRAME 1436:36576 appears in expectedNodes with fillHex #f0f0f0', () => {
    const entryWithChipFrame = manifest.find((e) =>
      Array.isArray(e.expectedNodes) &&
      e.expectedNodes.some((n) => n.figmaNodeId === '1436:36576'),
    )

    expect(
      entryWithChipFrame,
      'Expected to find at least one manifest entry whose expectedNodes contains node 1436:36576 (chip frame)',
    ).toBeDefined()

    const chipFrameNode = entryWithChipFrame!.expectedNodes!.find(
      (n) => n.figmaNodeId === '1436:36576',
    )!

    expect(chipFrameNode.fillHex?.toLowerCase()).toBe('#f0f0f0')
  })

  it('expectedNodes records have the required shape', () => {
    // Pick the first entry that has at least one expectedNode to validate shape
    const entryWithNodes = manifest.find(
      (e) => Array.isArray(e.expectedNodes) && e.expectedNodes.length > 0,
    )

    expect(entryWithNodes, 'Expected at least one entry with non-empty expectedNodes').toBeDefined()

    const node = entryWithNodes!.expectedNodes![0]
    expect(node).toHaveProperty('figmaNodeId')
    expect(node).toHaveProperty('name')
    expect(node).toHaveProperty('type')
    expect(node).toHaveProperty('fillHex')
    expect(node).toHaveProperty('borderHex')
    expect(node).toHaveProperty('textColorHex')
    expect(node).toHaveProperty('radius')
    expect(node).toHaveProperty('opacity')
    expect(typeof node.opacity).toBe('number')
  })

  it('nodes with no paint (fillHex and borderHex both null) are filtered out', () => {
    // All returned nodes must have at least one of fillHex or borderHex non-null
    const violatingEntries: string[] = []
    for (const entry of manifest) {
      for (const node of entry.expectedNodes ?? []) {
        if (node.fillHex == null && node.borderHex == null) {
          violatingEntries.push(`${entry.manifestId} → ${node.figmaNodeId} (${node.name})`)
        }
      }
    }
    expect(
      violatingEntries.length,
      `expectedNodes should not contain pure-geometry (no-paint) nodes. Violations:\n${violatingEntries.slice(0, 10).join('\n')}`,
    ).toBe(0)
  })

  it('TEXT node has non-null textColorHex when it has a fill', () => {
    // For any TEXT node in expectedNodes that has a non-null fillHex,
    // textColorHex should equal fillHex.
    const violations: string[] = []
    for (const entry of manifest) {
      for (const node of entry.expectedNodes ?? []) {
        if (node.type === 'TEXT' && node.fillHex != null && node.textColorHex !== node.fillHex) {
          violations.push(
            `${entry.manifestId} → ${node.figmaNodeId}: fillHex=${node.fillHex} but textColorHex=${node.textColorHex}`,
          )
        }
      }
    }
    expect(
      violations.length,
      `TEXT nodes must have textColorHex === fillHex. Violations:\n${violations.slice(0, 10).join('\n')}`,
    ).toBe(0)
  })

  it('non-TEXT nodes have textColorHex === null', () => {
    const violations: string[] = []
    for (const entry of manifest) {
      for (const node of entry.expectedNodes ?? []) {
        if (node.type !== 'TEXT' && node.textColorHex !== null) {
          violations.push(
            `${entry.manifestId} → ${node.figmaNodeId} (${node.type}): expected textColorHex=null but got ${node.textColorHex}`,
          )
        }
      }
    }
    expect(
      violations.length,
      `Non-TEXT nodes must have textColorHex=null. Violations:\n${violations.slice(0, 10).join('\n')}`,
    ).toBe(0)
  })
})
