import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { act, render } from '@testing-library/react'
import {
  clearAllWidgetStores,
  createWidgetStore,
  registerWidgetStore,
  getWidgetStore,
} from './widget-store-registry'
import {
  useSingleTransform,
  useTransform,
  type TransformControls,
} from './use-transform'
import { applyTransforms } from './transforms'
import type { TransformDescriptor, TransformFn } from './types'

function setup(id = 'wt-test') {
  const store = createWidgetStore(id, { data: 1 })
  registerWidgetStore(id, store)
  return store
}

const captureRef: { current: TransformControls | null } = { current: null }
const onControls = (c: TransformControls): void => {
  captureRef.current = c
}
const getCaptured = (): TransformControls | null => captureRef.current

function Single({
  id,
  descriptor,
  fn,
  initialEnabled,
}: {
  id: string
  descriptor: TransformDescriptor
  fn: TransformFn
  initialEnabled?: boolean
}) {
  const controls = useSingleTransform(id, descriptor, fn, { initialEnabled })
  onControls(controls)
  return null
}

function Multi({
  id,
  pairs,
  initialEnabled,
}: {
  id: string
  pairs: readonly { descriptor: TransformDescriptor; fn: TransformFn }[]
  initialEnabled?: boolean
}) {
  const controls = useTransform(id, pairs, { initialEnabled })
  onControls(controls)
  return null
}

beforeEach(() => {
  clearAllWidgetStores()
  captureRef.current = null
})

afterEach(() => {
  clearAllWidgetStores()
})

describe('useSingleTransform', () => {
  it('registers the transform on mount with enabled=true by default', () => {
    setup()
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => (n as number) * 2}
      />,
    )
    expect(captureRef.current?.enabled).toBe(true)
    expect(
      getWidgetStore('wt-test')
        .getState()
        .dataTransforms.map((t) => t.id),
    ).toEqual(['a'])
    expect(getWidgetStore('wt-test').getState().data).toBe(2)
  })

  it('respects initialEnabled=false', () => {
    setup()
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => (n as number) * 2}
        initialEnabled={false}
      />,
    )
    expect(captureRef.current?.enabled).toBe(false)
    expect(getWidgetStore('wt-test').getState().data).toBe(1)
  })

  it('toggle flips enabled and re-runs the pipeline', () => {
    setup()
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => (n as number) * 2}
      />,
    )
    act(() => captureRef.current?.toggle())
    expect(captureRef.current?.enabled).toBe(false)
    expect(getWidgetStore('wt-test').getState().data).toBe(1)
    act(() => captureRef.current?.toggle())
    expect(captureRef.current?.enabled).toBe(true)
    expect(getWidgetStore('wt-test').getState().data).toBe(2)
  })

  it('persists enabled to transformStates', () => {
    setup()
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => n}
      />,
    )
    act(() => captureRef.current?.setEnabled(false))
    expect(
      getWidgetStore('wt-test').getState().transformStates.a,
    ).toMatchObject({ enabled: false })
  })

  it('rehydrates enabled from transformStates on remount', () => {
    setup()
    const { unmount } = render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => n}
        initialEnabled={true}
      />,
    )
    act(() => captureRef.current?.setEnabled(false))
    unmount()
    captureRef.current = null
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => n}
        initialEnabled={true}
      />,
    )
    expect(getCaptured()?.enabled).toBe(false)
  })

  it('cleanup removes the transform on unmount', () => {
    setup()
    const { unmount } = render(
      <Single
        id='wt-test'
        descriptor={{ id: 'a', type: 'data', order: 1 }}
        fn={(n) => n}
      />,
    )
    expect(getWidgetStore('wt-test').getState().dataTransforms).toHaveLength(1)
    unmount()
    expect(getWidgetStore('wt-test').getState().dataTransforms).toHaveLength(0)
  })

  it('updates the registered fn when fn identity changes across renders', () => {
    setup()
    function Wrap({ multiplier }: { multiplier: number }) {
      return (
        <Single
          id='wt-test'
          descriptor={{ id: 'a', type: 'data', order: 1 }}
          fn={(n) => (n as number) * multiplier}
        />
      )
    }
    const { rerender } = render(<Wrap multiplier={2} />)
    expect(getWidgetStore('wt-test').getState().data).toBe(2)
    rerender(<Wrap multiplier={5} />)
    expect(getWidgetStore('wt-test').getState().data).toBe(5)
  })

  it('config-type descriptor registers into configTransforms', () => {
    setup()
    render(
      <Single
        id='wt-test'
        descriptor={{ id: 'c', type: 'config', order: 1 }}
        fn={(o) => ({ ...(o as object), x: 1 })}
      />,
    )
    const cfgTransforms = getWidgetStore('wt-test').getState().configTransforms
    expect(cfgTransforms.map((t) => t.id)).toEqual(['c'])
    expect(applyTransforms({}, cfgTransforms)).toEqual({ x: 1 })
  })
})

describe('useTransform (multi)', () => {
  it('registers paired data + config transforms with shared enabled', () => {
    setup()
    render(
      <Multi
        id='wt-test'
        pairs={[
          {
            descriptor: { id: 'rd-data', type: 'data', order: 1 },
            fn: (n) => (n as number) + 10,
          },
          {
            descriptor: { id: 'rd-config', type: 'config', order: 1 },
            fn: (o) => ({ ...(o as object), tag: 'rel' }),
          },
        ]}
      />,
    )
    const s = getWidgetStore('wt-test').getState()
    expect(s.dataTransforms.map((t) => t.id)).toEqual(['rd-data'])
    expect(s.configTransforms.map((t) => t.id)).toEqual(['rd-config'])
    expect(s.data).toBe(11)
    expect(applyTransforms({}, s.configTransforms)).toEqual({ tag: 'rel' })
  })

  it('toggle affects every paired transform together', () => {
    setup()
    render(
      <Multi
        id='wt-test'
        pairs={[
          {
            descriptor: { id: 'rd-data', type: 'data', order: 1 },
            fn: (n) => (n as number) + 10,
          },
          {
            descriptor: { id: 'rd-config', type: 'config', order: 1 },
            fn: (o) => ({ ...(o as object), tag: 'rel' }),
          },
        ]}
      />,
    )
    act(() => captureRef.current?.toggle())
    const s = getWidgetStore('wt-test').getState()
    expect(s.dataTransforms.every((t) => !t.enabled)).toBe(true)
    expect(s.configTransforms.every((t) => !t.enabled)).toBe(true)
    expect(s.data).toBe(1)
    // All config transforms disabled — applying them to {} leaves it untouched.
    expect(applyTransforms({}, s.configTransforms)).toEqual({})
  })

  it('throws when no pair is provided', () => {
    setup()
    function NoPairs() {
      useTransform('wt-test', [])
      return null
    }
    expect(() => render(<NoPairs />)).toThrow(/at least one pair/)
  })
})
