// @vitest-environment jsdom
//
// `/` — the SPA bill-splitter. A 100% client-side page (`renderMode='spa'`)
// built from React state + localStorage; no rpc client, no loader, no
// framework hooks. We assert the config export, that it renders, and — the
// real observable behavior — that entering a bill + choosing a tip preset +
// people count updates the computed per-person total.

import { afterEach, beforeEach, describe, expect, test } from 'vitest'
import { act, createElement, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { I18nProvider } from '@voltro/i18n'
import enCatalog from '../locales/en'

import BillSplitter, { renderMode } from './page'

;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true

let container: HTMLDivElement
let root: Root

// The page uses <T>, so it needs an <I18nProvider>. Wrap with the real English
// catalog (the framework auto-wires this provider in the running app).
const render = (node: ReactNode): void => {
  container = document.createElement('div')
  document.body.appendChild(container)
  act(() => {
    root = createRoot(container)
    root.render(
      createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }),
    )
  })
}

// Native value setter so React's onChange sees the programmatic update.
const setInput = (el: HTMLInputElement, value: string): void => {
  const setter = Object.getOwnPropertyDescriptor(
    window.HTMLInputElement.prototype,
    'value',
  )!.set!
  act(() => {
    setter.call(el, value)
    el.dispatchEvent(new Event('input', { bubbles: true }))
  })
}

// This Node build's jsdom does not provide localStorage (it needs
// --localstorage-file). The page talks to the standard Web Storage API, so we
// install a spec-compliant in-memory Storage — the same surface a browser gives
// it — and exercise the real persistence path against it.
let hadLocalStorage: boolean

beforeEach(() => {
  hadLocalStorage = typeof window.localStorage !== 'undefined'
  if (!hadLocalStorage) {
    const store = new Map<string, string>()
    const storage: Storage = {
      get length() {
        return store.size
      },
      clear: () => store.clear(),
      getItem: (k) => (store.has(k) ? store.get(k)! : null),
      key: (i) => Array.from(store.keys())[i] ?? null,
      removeItem: (k) => {
        store.delete(k)
      },
      setItem: (k, v) => {
        store.set(k, String(v))
      },
    }
    Object.defineProperty(window, 'localStorage', { configurable: true, value: storage })
  }
  window.localStorage.clear()
})

afterEach(() => {
  if (root) act(() => root.unmount())
  container?.remove()
  document.body.innerHTML = ''
  if (!hadLocalStorage) {
    Reflect.deleteProperty(window, 'localStorage')
  }
})

describe('index — config export', () => {
  test('is a client-only SPA page', () => {
    expect(renderMode).toBe('spa')
  })
})

describe('index — render', () => {
  test('renders the bill-splitter heading and the tip presets', () => {
    render(createElement(BillSplitter))
    expect(container.querySelector('h1')!.textContent).toBe('Bill splitter')
    const presetButtons = Array.from(
      container.querySelectorAll('.presets button'),
    ).map((b) => b.textContent)
    expect(presetButtons).toEqual(['10%', '15%', '18%', '20%'])
  })
})

describe('index — computed split', () => {
  test('computes the per-person total from bill, tip preset, and people', () => {
    render(createElement(BillSplitter))

    // $100 bill.
    setInput(container.querySelector('#bill') as HTMLInputElement, '100')

    // Choose the 20% preset.
    const twentyPct = Array.from(container.querySelectorAll('.presets button')).find(
      (b) => b.textContent === '20%',
    ) as HTMLButtonElement
    act(() => { twentyPct.dispatchEvent(new MouseEvent('click', { bubbles: true })) })

    // 4 people.
    setInput(container.querySelector('#people') as HTMLInputElement, '4')

    // total = 100 + 20% = 120; per-person = 30.
    const perPerson = container.querySelector('.result .big')!.textContent ?? ''
    expect(perPerson).toContain('30')
    expect(container.textContent).toContain('120') // total tip+bill shown
  })

  test('persists the chosen tip % to localStorage', () => {
    render(createElement(BillSplitter))
    const tenPct = Array.from(container.querySelectorAll('.presets button')).find(
      (b) => b.textContent === '10%',
    ) as HTMLButtonElement
    act(() => { tenPct.dispatchEvent(new MouseEvent('click', { bubbles: true })) })
    const stored: string[] = []
    for (let i = 0; i < window.localStorage.length; i++) {
      const key = window.localStorage.key(i)
      if (key !== null) stored.push(window.localStorage.getItem(key) ?? '')
    }
    expect(stored).toContain('10')
  })
})
