import type { ReactNode } from 'react'
import type { ListFilterNodeType } from '../types'
import type useConfig from '../use-config'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
import Panel from '../panel'
import { OrderBy } from '../types'

const mockUseConfig = vi.hoisted(() => vi.fn())
const mockSwitch = vi.hoisted(() => vi.fn())
const mockVarReferencePicker = vi.hoisted(() => vi.fn())
const mockFilterCondition = vi.hoisted(() => vi.fn())
const mockExtractInput = vi.hoisted(() => vi.fn())
const mockLimitConfig = vi.hoisted(() => vi.fn())
const mockSubVariablePicker = vi.hoisted(() => vi.fn())
const mockOptionCard = vi.hoisted(() => vi.fn())

vi.mock('../use-config', () => ({
  __esModule: true,
  default: (...args: unknown[]) => mockUseConfig(...args),
}))

vi.mock('@langgenius/dify-ui/switch', () => ({
  Switch: (props: {
    checked?: boolean
    disabled?: boolean
    onCheckedChange: (value: boolean) => void
  }) => {
    mockSwitch(props)
    return (
      <button
        type="button"
        role="switch"
        aria-checked={props.checked}
        disabled={props.disabled}
        onClick={() => props.onCheckedChange(!props.checked)}
      >
        {props.disabled ? 'switch:disabled' : 'switch:enabled'}
      </button>
    )
  },
}))

vi.mock('@/app/components/workflow/nodes/_base/components/variable/var-reference-picker', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    value: string[]
    onChange: (value: string[]) => void
  }) => {
    mockVarReferencePicker(props)
    return (
      <button type="button" onClick={() => props.onChange(['node-2', 'records'])}>
        {props.readonly ? 'var-picker:readonly' : `var-picker:${props.value.join('.')}`}
      </button>
    )
  },
}))

vi.mock('../components/filter-condition', () => ({
  __esModule: true,
  default: (props: {
    readOnly: boolean
    onChange: (value: { key: string }) => void
  }) => {
    mockFilterCondition(props)
    return (
      <button type="button" onClick={() => props.onChange({ key: 'size' })}>
        {props.readOnly ? 'filter-condition:readonly' : 'filter-condition:editable'}
      </button>
    )
  },
}))

vi.mock('../components/extract-input', () => ({
  __esModule: true,
  default: (props: {
    value: string
    readOnly: boolean
    onChange: (value: string) => void
  }) => {
    mockExtractInput(props)
    return (
      <button type="button" onClick={() => props.onChange('2')}>
        {props.readOnly ? 'extract-input:readonly' : `extract-input:${props.value}`}
      </button>
    )
  },
}))

vi.mock('../components/limit-config', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    config: { enabled: boolean, size?: number }
    onChange: (config: { enabled: boolean, size?: number }) => void
  }) => {
    mockLimitConfig(props)
    return (
      <button
        type="button"
        onClick={() => props.onChange({ enabled: true, size: (props.config.size || 0) + 1 })}
      >
        {props.readonly ? 'limit-config:readonly' : `limit-config:${props.config.size || 0}`}
      </button>
    )
  },
}))

vi.mock('../components/sub-variable-picker', () => ({
  __esModule: true,
  default: (props: {
    value: string
    onChange: (value: string) => void
  }) => {
    mockSubVariablePicker(props)
    return (
      <button type="button" onClick={() => props.onChange('name')}>
        {`sub-variable:${props.value || 'empty'}`}
      </button>
    )
  },
}))

vi.mock('../../_base/components/option-card', () => ({
  __esModule: true,
  default: (props: {
    title: string
    selected: boolean
    onSelect: () => void
  }) => {
    mockOptionCard(props)
    return (
      <button type="button" onClick={props.onSelect}>
        {`${props.title}:${props.selected ? 'selected' : 'idle'}`}
      </button>
    )
  },
}))

vi.mock('../../_base/components/output-vars', () => ({
  __esModule: true,
  default: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  VarItem: ({ name, type, description }: { name: string, type: string, description: string }) => (
    <div>{`${name}:${type}:${description}`}</div>
  ),
}))

const createData = (overrides: Partial<ListFilterNodeType> = {}): ListFilterNodeType => ({
  title: 'List Operator',
  desc: '',
  type: BlockEnum.ListFilter,
  variable: ['answer-node', 'items'],
  var_type: VarType.arrayObject,
  item_var_type: VarType.object,
  filter_by: {
    enabled: true,
    conditions: [{
      key: 'name',
      comparison_operator: 'contains' as never,
      value: '',
    }],
  },
  extract_by: {
    enabled: true,
    serial: '1',
  },
  order_by: {
    enabled: true,
    key: 'size',
    value: OrderBy.ASC,
  },
  limit: {
    enabled: true,
    size: 10,
  },
  ...overrides,
})

const createConfigResult = (overrides: Partial<ReturnType<typeof useConfig>> = {}): ReturnType<typeof useConfig> => ({
  readOnly: false,
  inputs: createData(),
  varType: VarType.arrayObject,
  itemVarType: VarType.object,
  itemVarTypeShowName: 'Object',
  hasSubVariable: true,
  handleVarChanges: vi.fn(),
  filterVar: vi.fn(() => true),
  handleFilterEnabledChange: vi.fn(),
  handleFilterChange: vi.fn(),
  handleExtractsEnabledChange: vi.fn(),
  handleExtractsChange: vi.fn(),
  handleLimitChange: vi.fn(),
  handleOrderByEnabledChange: vi.fn(),
  handleOrderByKeyChange: vi.fn(),
  handleOrderByTypeChange: vi.fn((value: OrderBy) => () => value),
  ...overrides,
})

const renderPanel = (data: ListFilterNodeType = createData()) => {
  const props: NodePanelProps<ListFilterNodeType> = {
    id: 'list-node',
    data,
    panelProps: {
      getInputVars: vi.fn(() => []),
      toVarInputs: vi.fn(() => []),
      runInputData: {},
      runInputDataRef: { current: {} },
      setRunInputData: vi.fn(),
      runResult: null,
    },
  }

  return render(<Panel {...props} />)
}

describe('list-operator/panel', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockUseConfig.mockReturnValue(createConfigResult())
  })

  it('renders enabled sections and forwards all main interactions', async () => {
    const user = userEvent.setup()
    const config = createConfigResult({
      handleOrderByTypeChange: vi.fn((value: OrderBy) => () => config.handleOrderByEnabledChange(value === OrderBy.ASC)),
    })
    mockUseConfig.mockReturnValue(config)

    renderPanel()

    expect(screen.getByText('workflow.nodes.listFilter.inputVar')).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'var-picker:answer-node.items' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'filter-condition:editable' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'extract-input:1' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'limit-config:10' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'sub-variable:size' })).toBeInTheDocument()
    expect(screen.getByText('result:Array[Object]:workflow.nodes.listFilter.outputVars.result')).toBeInTheDocument()
    expect(screen.getByText('first_record:Object:workflow.nodes.listFilter.outputVars.first_record')).toBeInTheDocument()
    expect(screen.getByText('last_record:Object:workflow.nodes.listFilter.outputVars.last_record')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: 'var-picker:answer-node.items' }))
    await user.click(screen.getByRole('button', { name: 'filter-condition:editable' }))
    await user.click(screen.getByRole('button', { name: 'extract-input:1' }))
    await user.click(screen.getByRole('button', { name: 'limit-config:10' }))
    await user.click(screen.getByRole('button', { name: 'sub-variable:size' }))
    await user.click(screen.getAllByRole('switch')[0]!)
    await user.click(screen.getAllByRole('switch')[1]!)
    await user.click(screen.getAllByRole('switch')[2]!)
    await user.click(screen.getByRole('button', { name: 'workflow.nodes.listFilter.asc:selected' }))
    await user.click(screen.getByRole('button', { name: 'workflow.nodes.listFilter.desc:idle' }))

    expect(config.handleVarChanges).toHaveBeenCalledWith(['node-2', 'records'])
    expect(config.handleFilterChange).toHaveBeenCalledWith({ key: 'size' })
    expect(config.handleExtractsChange).toHaveBeenCalledWith('2')
    expect(config.handleLimitChange).toHaveBeenCalledWith({ enabled: true, size: 11 })
    expect(config.handleOrderByKeyChange).toHaveBeenCalledWith('name')
    expect(config.handleFilterEnabledChange).toHaveBeenCalledWith(false)
    expect(config.handleExtractsEnabledChange).toHaveBeenCalledWith(false)
    expect(config.handleOrderByEnabledChange).toHaveBeenCalled()
    expect(config.handleOrderByTypeChange).toHaveBeenCalledWith(OrderBy.ASC)
    expect(config.handleOrderByTypeChange).toHaveBeenCalledWith(OrderBy.DESC)
  })

  it('hides disabled sections and forwards readonly state to child controls', () => {
    mockUseConfig.mockReturnValue(createConfigResult({
      readOnly: true,
      hasSubVariable: false,
      inputs: createData({
        filter_by: {
          enabled: false,
          conditions: [],
        },
        extract_by: {
          enabled: false,
          serial: '',
        },
        order_by: {
          enabled: false,
          key: '',
          value: OrderBy.DESC,
        },
      }),
    }))

    renderPanel()

    expect(screen.getByRole('button', { name: 'var-picker:readonly' })).toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'limit-config:readonly' })).toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'filter-condition:readonly' })).not.toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'extract-input:' })).not.toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'sub-variable:empty' })).not.toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'workflow.nodes.listFilter.asc:idle' })).not.toBeInTheDocument()
    expect(screen.getAllByRole('switch')).toHaveLength(3)
    expect(screen.getAllByRole('switch').every(button => button.hasAttribute('disabled'))).toBe(true)
  })
})
