// tests/demo-renderer-react.test.ts
//
// NOTE on render helper: the task brief's Step 3 draft imports `render` from
// `@testing-library/react`, aligning with `tests/react-island.test.ts`'s "RTL
// usage" framing. That package is only a devDependency of `react-pilot/`'s
// own package.json — it is NOT installed at the repo root, and these tests
// run under the ROOT vitest config (`tests/**/*.{test,spec}.{ts,mts}`),
// which does not have it in `node_modules`. Adding it would mean touching
// package.json/the lockfile, outside this task's 3-file scope. `react` and
// `react-dom` ARE root deps, so this uses `react-dom/client`'s `createRoot`
// + `act` (both stable, from 'react-dom/client' and 'react' respectively in
// React 19) as a dependency-free drop-in for RTL's `render(...).container`.
import { act, createElement } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, describe, expect, it } from 'vitest'

// Tell React this jsdom environment supports `act()` (silences the
// "not configured to support act(...)" warning `createRoot`+`act` would
// otherwise print — same flag React Testing Library sets internally).
;(globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
import { DemoRenderer } from '../react-pilot/src/demos/DemoRenderer'
import { progressDescriptor } from '../react-pilot/src/demos/descriptors/progress'
import { buttonDescriptor } from '../react-pilot/src/demos/descriptors/button'
import { topbarDescriptor } from '../react-pilot/src/demos/descriptors/topbar'
import { resolveIcon } from '../react-pilot/src/demos/resolve-icon'
import '@tvu/wc' // side-effect: register CE (tvu-button etc.) — needed for Button descriptor tests

function renderIntoContainer(node: any) {
  const container = document.createElement('div')
  document.body.appendChild(container)
  const root: Root = createRoot(container)
  act(() => {
    root.render(node)
  })
  return { container, root }
}

let mounted: { container: HTMLElement; root: Root }[] = []
function render(node: any) {
  const m = renderIntoContainer(node)
  mounted.push(m)
  return m
}

afterEach(() => {
  for (const { container, root } of mounted) {
    act(() => root.unmount())
    container.remove()
  }
  mounted = []
})

describe('DemoRenderer (react)', () => {
  it('renders 6 sections + figma-members-grid + family cells + code + range', () => {
    const { container } = render(
      createElement(DemoRenderer, { descriptor: progressDescriptor, theme: 'dark', locale: 'en-US' }),
    )
    expect([...container.querySelectorAll('.docs-section__title')].map((n) => n.textContent)).toEqual([
      'Figma Coverage',
      'Family Matrix',
      'Runtime Value',
      'Development Usage',
      'Status Matrix',
      'Interactive: showLabel',
    ])
    expect(container.querySelector('.figma-members-grid')).toBeTruthy()
    expect(container.querySelectorAll('.family-matrix__cell').length).toBeGreaterThan(0)
    expect(container.querySelector('.code-block')).toBeTruthy()
    // INFRA-F148: both halves matter — the first fails if the swap never happened,
    // the second fails if it happened but left the old element behind.
    expect(container.querySelector('tvu-slider')).toBeTruthy()
    expect(container.querySelector('input.try-range')).toBeNull()
  })
})

describe('DemoRenderer (react) — Button', () => {
  function renderButton() {
    return render(createElement(DemoRenderer, { descriptor: buttonDescriptor, theme: 'dark', locale: 'en-US' }))
  }

  it('renders all 9 axis-section titles + 2 info-section titles', () => {
    const { container } = renderButton()
    expect([...container.querySelectorAll('.docs-section__title')].map((n) => n.textContent)).toEqual([
      'Figma M Coverage', 'Development Usage',
      'Basic Usage', 'Color Buttons', 'Status Buttons', 'Disable Buttons', 'Icon Buttons',
      'Loading Buttons', 'Round Buttons', 'Button Sizes', 'Fixed Width Buttons',
    ])
  })

  it('renders the default "Button" text inside instances', () => {
    const { container } = renderButton()
    const samples = container.querySelectorAll('.review-sample')
    expect(samples.length).toBeGreaterThan(0)
    expect(samples[0].textContent).toContain('Button')
  })

  it('renders the code-block card summary paragraph before the <pre>', () => {
    const { container } = renderButton()
    const codeCard = container.querySelector('.code-block')!.closest('.docs-demo-card')!
    const summary = codeCard.querySelector('.docs-demo-card__summary')
    expect(summary?.textContent).toContain('real Button entry point')
  })

  it('hover flips a default-status sample to status=hover (focusin/focusout — bubbling equivalent of mouseenter/mouseleave)', () => {
    const { container } = renderButton()
    const sample = container.querySelectorAll('.review-sample')[0] as HTMLElement
    const btn = () => sample.querySelector('tvu-button') as any
    expect(btn().status).toBe('default')
    act(() => {
      sample.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
    })
    expect(btn().status).toBe('hover')
    act(() => {
      sample.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
    })
    expect(btn().status).toBe('default')
  })

  it('does not bind hover on disable-review samples (status stays disable)', () => {
    const { container } = renderButton()
    const sections = [...container.querySelectorAll('.docs-section')]
    const disableSection = sections.find((s) => s.querySelector('.docs-section__title')?.textContent === 'Disable Buttons')!
    expect(disableSection.querySelector('.review-demo__hint')).toBeNull()
    const sample = disableSection.querySelectorAll('.review-sample')[0] as HTMLElement
    const btn = sample.querySelector('tvu-button') as any
    act(() => {
      sample.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
    })
    expect(btn.status).toBe('disable')
  })

  it('renders the loading matrix with 40 members (variant + node-id text)', () => {
    const { container } = renderButton()
    const variants = container.querySelectorAll('.loading-matrix__variant')
    const nodes = container.querySelectorAll('.loading-matrix__node')
    expect(variants).toHaveLength(40)
    expect(nodes).toHaveLength(40)
    expect(variants[0].textContent).toContain('icon=loading')
    expect(nodes[0].textContent).toBe('337:12307')
  })

  it('layout: "bare" axis sections render with no .docs-demo-grid ancestor', () => {
    const { container } = renderButton()
    const sections = [...container.querySelectorAll('.docs-section')]
    const basicSection = sections.find((s) => s.querySelector('.docs-section__title')?.textContent === 'Basic Usage')!
    expect(basicSection.querySelector('.docs-demo-grid')).toBeNull()
    expect(basicSection.querySelector('.review-demo')).toBeTruthy()
    const infoSection = sections.find((s) => s.querySelector('.docs-section__title')?.textContent === 'Figma M Coverage')!
    expect(infoSection.querySelector('.docs-demo-grid')).toBeTruthy()
  })
})

describe('DemoRenderer (react) — TopBar', () => {
  function renderTopBar() {
    return render(createElement(DemoRenderer, { descriptor: topbarDescriptor, theme: 'dark', locale: 'en-US' }))
  }

  // INFRA-F148 (2026-09-14) removed the `typeInto` helper that lived here. It routed
  // writes through `HTMLInputElement.prototype`'s value setter to satisfy React's
  // change detection on a **native** <input>. The only caller was the Try-it text
  // control, which is a DS `<tvu-input>` CE now — its wrapper listens for the Vue
  // `update:modelValue` CustomEvent, not for a native input event, so the helper had
  // no remaining caller. ⛔ Don't reinstate it to "type into" a CE: that would test a
  // path the wrapper does not use.

  it('renders all 4 section titles in order', () => {
    const { container } = renderTopBar()
    expect([...container.querySelectorAll('.docs-section__title')].map((n) => n.textContent)).toEqual([
      'Figma Coverage', 'Development Usage', 'Figma Members', 'Interactive: showMenu',
    ])
  })

  it('After-Login member: meta header, real tvu-logo CE, real Icon-resolved svg', () => {
    const { container } = renderTopBar()
    const metas = [...container.querySelectorAll('.topbar-member__meta')]
    const afterMeta = metas.find((m) => m.textContent?.includes('Tag=After Login'))!
    expect(afterMeta.textContent).toContain('4771:7174')
    const card = afterMeta.closest('.docs-demo-card')!
    expect(card.querySelector('tvu-logo')).toBeTruthy()
    expect(card.querySelector('img')).toBeNull()
    const gridIcon = card.querySelector('.topbar-grid-icon')
    expect(gridIcon).toBeTruthy()
    expect(gridIcon!.querySelector('svg')).toBeTruthy()
  })

  it('Before-Login member: primary CTA present, no timezone block', () => {
    const { container } = renderTopBar()
    const metas = [...container.querySelectorAll('.topbar-member__meta')]
    const beforeMeta = metas.find((m) => m.textContent?.includes('Tag=Before Login'))!
    expect(beforeMeta.textContent).toContain('4771:7226')
    const card = beforeMeta.closest('.docs-demo-card')!
    expect(card.querySelector('.topbar-action--primary')).toBeTruthy()
    expect(card.querySelector('.timezone-block')).toBeNull()
  })

  it('Try-it card: initial readout, cycle control flips tag + right-content, text control updates title', () => {
    const { container } = renderTopBar()
    const readout = () => container.querySelector('.try-readout')!.textContent!
    expect(readout()).toContain('My Application')
    expect(readout()).toContain('After Login')

    const tryItCard = container.querySelector('.try-readout')!.closest('.docs-demo-card')!
    expect(tryItCard.querySelector('.topbar-action--primary')).toBeNull()

    // INFRA-F148: cycle = <tvu-button>, text = <tvu-input>. The CEs are NOT upgraded
    // under jsdom, so drive them exactly the way the generated wrappers listen:
    // a real click on the host element, and the `update:modelValue` CustomEvent the
    // Vue-authored CE emits (detail is the emit-args array — see wrappers/Input.tsx).
    const controls = tryItCard.querySelector('.try-controls')!
    expect(controls.querySelector('.try-button')).toBeNull()
    expect(controls.querySelector('input.try-input')).toBeNull()

    const cycleBtn = controls.querySelector('tvu-button')!
    act(() => {
      cycleBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    })
    expect(readout()).toContain('Before Login')
    expect(tryItCard.querySelector('.topbar-action--primary')).toBeTruthy()

    const textInput = controls.querySelector('tvu-input')!
    act(() => {
      textInput.dispatchEvent(new CustomEvent('update:modelValue', { detail: ['Renamed App'] }))
    })
    expect(readout()).toContain('Renamed App')
  })

  it('Interactive: showMenu card renders 2 tvu-switch controls, no native checkbox', () => {
    const { container } = renderTopBar()
    const sections = [...container.querySelectorAll('.docs-section')]
    const showMenuSection = sections.find((s) => s.querySelector('.docs-section__title')?.textContent === 'Interactive: showMenu')!
    expect(showMenuSection.querySelectorAll('tvu-switch').length).toBe(2)
    expect(showMenuSection.querySelector('input[type="checkbox"]')).toBeNull()
  })
})

describe('resolveIcon (react demo helper, D-2)', () => {
  it('resolves a registry name to raw.ts svg markup (smoke — Task 8 will exercise it for real)', () => {
    const node = resolveIcon('navigation/app-launcher')
    const { container } = render(node)
    expect(container.innerHTML).toContain('<svg')
  })
})
