// @vitest-environment jsdom
//
// `/` — the collaborative editor, driven through `makeVoltroTestClient`
// (`@voltro/testing/client`) rather than a hand-rolled `vi.mock('@voltro/client')`.
//
// WHY THE HARNESS HERE and not a mock. This page is stateful: `useCrdtText`
// owns a sync client per document cell, seeded from the server body and
// folding every INCOMING body back in. A `vi.mock` can return one snapshot per
// mount, so the fold — the whole point of the page — was untestable, and the
// suite that replaced this one never pushed a second value into a mounted
// tree. `setSubscription` is a real server delta, so the last case below
// asserts convergence.
//
// Two other things fall out of it: the real `@voltro/client` hooks run (so a
// change in their loading/error semantics is caught here instead of being
// re-implemented by the mock), and the page can be imported statically —
// `await import('./page')` only existed to sequence around `vi.mock`'s hoist.
//
// `@voltro/local-first` is NOT faked either: the real `useCrdtText` hook
// decodes the seeded body and turns keystrokes into encoded updates (via its
// `crdtTextEdit` span diff), which is the behaviour worth asserting.

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 { crdtText, type CrdtState } from '@voltro/local-first'
import { makeVoltroTestClient, type VoltroTestClient } from '@voltro/testing/client'
import enCatalog from '../locales/en'
import IndexPage from './page'

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

const LIST = 'documents.list'

let container: HTMLDivElement
let root: Root
let client: VoltroTestClient

/** A CRDT state holding `text`, from a fresh document. */
const bodyWith = (text: string): CrdtState => crdtText().insert(0, text).encode()

/** A REMOTE edit derived from `base`, so merging it back converges instead of
 *  interleaving two independently-created runs of the same characters. */
const remoteEditOf = (base: CrdtState, at: number, insert: string): CrdtState =>
  crdtText().merge(base).insert(at, insert).encode()

const docRow = (over: Partial<{ id: string; title: string; body: CrdtState | null }> = {}) => ({
  id: over.id ?? 'doc_1',
  title: over.title ?? 'Shared document',
  body: over.body ?? null,
  tenantId: 'acme',
  createdAt: new Date(0),
})

const render = (): void => {
  container = document.createElement('div')
  document.body.appendChild(container)
  act(() => {
    root = createRoot(container)
    root.render(
      createElement(I18nProvider, {
        locale: 'en',
        messages: enCatalog,
        defaultLocale: 'en',
        children: createElement(client.Provider, null, createElement(IndexPage) as ReactNode),
      }),
    )
  })
}

const setTextarea = (el: HTMLTextAreaElement, value: string): void => {
  const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')!.set!
  act(() => {
    setter.call(el, value)
    el.dispatchEvent(new Event('input', { bubbles: true }))
  })
}

const textarea = (): HTMLTextAreaElement => container.querySelector('textarea') as HTMLTextAreaElement

/** The harness with both writes wired. Handlers return void; the assertions
 *  read `client.calls`, which records tag + input in order. */
const harness = (subscriptions: Readonly<Record<string, unknown>> = {}): VoltroTestClient =>
  makeVoltroTestClient({
    subscriptions,
    mutations: {
      'documents.create': () => undefined,
      'documents.setBody': () => undefined,
    },
  })

beforeEach(() => { client = harness() })

afterEach(() => {
  if (root) act(() => root.unmount())
  container?.remove()
  document.body.innerHTML = ''
})

describe('collab editor — subscription states', () => {
  test('shows the loading hint before the first snapshot', () => {
    // A tag with NO fixture is the harness's cold state — the real thing a
    // component sees before the first delta arrives.
    render()
    expect(container.textContent).toContain('loading…')
    expect(container.querySelector('textarea')).toBeNull()
  })

  test('shows the empty state + create button once a snapshot arrives with no documents', () => {
    client = harness({ [LIST]: [] })
    render()
    expect(container.textContent).toContain('No document yet')
    expect(container.querySelector('button')).not.toBeNull()
    expect(container.querySelector('textarea')).toBeNull()
  })

  test('renders the error banner when the subscription errors', () => {
    render()
    act(() => { client.failSubscription(LIST, 'boom') })
    expect(container.textContent).toContain('subscription error')
    expect(container.textContent).toContain('boom')
  })
})

describe('collab editor — create', () => {
  test('clicking Create document calls documents.create with the tenant + a title', async () => {
    client = harness({ [LIST]: [] })
    render()
    const button = container.querySelector('button') as HTMLButtonElement
    await act(async () => {
      button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    })
    expect(client.calls).toEqual([
      { kind: 'mutation', tag: 'documents.create', input: { tenantId: 'acme', title: 'Shared document' } },
    ])
  })
})

describe('collab editor — editing a document', () => {
  test('binds the textarea to the decoded CRDT body', () => {
    client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] })
    render()
    expect(textarea()).not.toBeNull()
    expect(textarea().value).toBe('Hello')
    expect(container.textContent).toContain('Shared document')
  })

  test('typing sends an encoded CRDT update via documents.setBody', () => {
    client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] })
    render()
    setTextarea(textarea(), 'Hello!')

    expect(client.calls).toHaveLength(1)
    const call = client.calls[0]!
    expect(call.tag).toBe('documents.setBody')
    const input = call.input as { id: string; update: unknown }
    expect(input.id).toBe('doc_1')
    expect(input.update).toBeInstanceOf(Uint8Array)
  })

  // The case the hand-rolled mock could not express, and the page's whole
  // reason to exist: a body edited in ANOTHER tab arrives as a delta, and the
  // local handle folds it in without remounting the editor.
  test('a remote edit pushed by the server converges into the open editor', () => {
    const base = bodyWith('Hello')
    client = harness({ [LIST]: [docRow({ body: base })] })
    render()
    expect(textarea().value).toBe('Hello')

    act(() => {
      client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 5, ' world') })])
    })

    expect(textarea().value).toBe('Hello world')
    // A fold is not a write — nothing is echoed back to the server.
    expect(client.calls).toEqual([])
  })

  test('a local edit survives a concurrent remote edit — both are in the merged text', () => {
    const base = bodyWith('Hello')
    client = harness({ [LIST]: [docRow({ body: base })] })
    render()
    setTextarea(textarea(), 'Hello!')
    expect(textarea().value).toBe('Hello!')

    // The other tab branched from the SAME base and appended elsewhere.
    act(() => {
      client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 0, 'Oh, ') })])
    })

    expect(textarea().value).toContain('Oh, ')
    expect(textarea().value).toContain('!')
  })
})
