import { useEffect } from 'react'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { act, render } from '@testing-library/react'
import type * as echarts from 'echarts'
import { Provider } from '../provider/widget-provider'
import { Echart } from './echart'
import type { OptionFactory } from './echart'
import {
  clearAllWidgetStores,
  getEchartInstance,
  getWidgetStore,
} from '../stores'

// Capture the option that EchartUI receives, since we don't render real ECharts in tests.
const captured: {
  option: echarts.EChartsOption | null
  replaceMerge: readonly string[] | null
  setOptions: number
} = {
  option: null,
  replaceMerge: null,
  setOptions: 0,
}

vi.mock('./echart-ui', () => ({
  EchartUI: ({
    option,
    replaceMerge,
    onInstance,
  }: {
    option: echarts.EChartsOption
    replaceMerge: readonly string[]
    onInstance?: (chart: echarts.ECharts | null) => void
  }) => {
    captured.option = option
    captured.replaceMerge = replaceMerge
    captured.setOptions++
    // Mimic the real bridge: publish a fake instance on mount, clear on
    // unmount. Cast through unknown so the test doesn't depend on the full
    // ECharts surface.
    useEffect(() => {
      const fake = { __mock: true } as unknown as echarts.ECharts
      onInstance?.(fake)
      return () => onInstance?.(null)
    }, [onInstance])
    return null
  },
  DEFAULT_INIT_OPTS: { renderer: 'svg', height: 304 },
}))

beforeEach(() => {
  clearAllWidgetStores()
  captured.option = null
  captured.replaceMerge = null
  captured.setOptions = 0
})
afterEach(() => clearAllWidgetStores())

// Helper: build an OptionFactory whose structural branch returns `structural`
// and whose merge branch passes the post-config-transforms option through
// `mergeData(option, data, ctx)`.
const makeFactory = (
  structural: echarts.EChartsOption,
  mergeData: (
    option: echarts.EChartsOption,
    data: unknown,
  ) => echarts.EChartsOption,
): OptionFactory => {
  return (option, data) => {
    if (option == null) return structural
    return mergeData(option, data)
  }
}

describe('<Echart> bridge — owns the option factory directly', () => {
  it('forwards the structural option to EchartUI synchronously on first render', () => {
    const factory = makeFactory(
      { xAxis: { type: 'category' } } as echarts.EChartsOption,
      // Merge branch: return the option as-is so the structural option
      // flows through to ECharts unchanged.
      (option) => option,
    )
    render(
      <Provider id='e1' data={[]}>
        <Echart optionFactory={factory} />
      </Provider>,
    )
    expect(captured.option).toEqual({ xAxis: { type: 'category' } })
    // No `rawOptions` on the store — the option pipeline is Echart-local now.
    expect(
      (getWidgetStore('e1').getState() as unknown as { rawOptions?: unknown })
        .rawOptions,
    ).toBeUndefined()
  })

  it('fuses state.data into the option via the factory merge branch (dataset)', () => {
    const factory = makeFactory(
      { xAxis: { type: 'category' } } as echarts.EChartsOption,
      (option, data) =>
        ({
          ...option,
          dataset: Array.isArray(data)
            ? (data as { name: string; value: number }[][]).map((source) => ({
                source,
              }))
            : [],
        }) as echarts.EChartsOption,
    )
    render(
      <Provider id='e2' data={[[{ name: 'A', value: 1 }]]}>
        <Echart optionFactory={factory} />
      </Provider>,
    )
    const captured2 = captured.option as { dataset: { source: unknown }[] }
    expect(captured2.dataset).toHaveLength(1)
    expect(captured2.dataset[0]?.source).toEqual([{ name: 'A', value: 1 }])
  })

  it('updates the option when a data transform changes state.data (no re-init)', () => {
    interface Row {
      name: string
      value: number
    }
    const factory = makeFactory(
      { xAxis: { type: 'category' } } as echarts.EChartsOption,
      (option, data) =>
        ({
          ...option,
          // Mark each dataset with the record count so changes are observable.
          dataset: Array.isArray(data)
            ? (data as Row[][]).map((s) => ({
                source: s,
                id: `len:${s.length}`,
              }))
            : [],
        }) as echarts.EChartsOption,
    )
    render(
      <Provider
        id='e3'
        data={[
          [
            { name: 'A', value: 1 },
            { name: 'B', value: 2 },
            { name: 'C', value: 3 },
          ],
        ]}
      >
        <Echart optionFactory={factory} />
      </Provider>,
    )

    // Before transform: 3 records.
    let dataset = (captured.option as { dataset: { id: string }[] }).dataset
    expect(dataset[0]?.id).toBe('len:3')

    // Register a data transform that filters out half the records.
    act(() => {
      getWidgetStore('e3').setState({
        dataTransforms: [
          {
            id: 'cut',
            type: 'data',
            order: 1,
            enabled: true,
            fn: (input) => {
              const arr = input as { name: string; value: number }[][]
              return arr.map((s) => s.slice(0, 1))
            },
          },
        ],
      })
    })

    // Bridge should have re-rendered with post-pipeline data: 1 record.
    dataset = (captured.option as { dataset: { id: string }[] }).dataset
    expect(dataset[0]?.id).toBe('len:1')
  })

  it('applies configTransforms to the rendered option and forwards replaceMerge', () => {
    const factory = makeFactory(
      { xAxis: { type: 'category' } } as echarts.EChartsOption,
      (option) => option,
    )
    render(
      <Provider id='e-cfg' data={[]}>
        <Echart optionFactory={factory} />
      </Provider>,
    )

    // Before transform: untouched structural; replaceMerge is the default.
    expect(captured.option).toEqual({ xAxis: { type: 'category' } })
    expect(captured.replaceMerge).toEqual(['toolbox'])

    // Register a config transform that stamps a field onto the option and
    // contributes a `replaceMergeKeys` entry.
    act(() => {
      getWidgetStore('e-cfg').setState({
        configTransforms: [
          {
            id: 'stamp',
            type: 'config',
            order: 1,
            enabled: true,
            replaceMergeKeys: ['stamp'],
            fn: (o) => ({ ...(o as object), stamp: true }),
          },
        ],
      })
    })

    // After transform: option carries the mutation, replaceMerge is
    // deduped+sorted with the default and the transform's keys.
    expect((captured.option as { stamp?: boolean }).stamp).toBe(true)
    expect(captured.replaceMerge).toEqual(['stamp', 'toolbox'])
  })

  it('publishes the live ECharts instance to the per-id registry on mount and clears on unmount', () => {
    const factory = makeFactory({} as echarts.EChartsOption, (option) => option)
    const { unmount } = render(
      <Provider id='e-instance' data={[]}>
        <Echart optionFactory={factory} />
      </Provider>,
    )
    expect(getEchartInstance('e-instance')).not.toBeNull()
    unmount()
    expect(getEchartInstance('e-instance')).toBeNull()
  })
})
