import type { ReactNode } from 'react'
import type useConfig from '../hooks/use-config'
import type { HumanInputNodeType } from '../types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { toast } from '@langgenius/dify-ui/toast'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import copy from 'copy-to-clipboard'
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
import Panel from '../panel'
import { DeliveryMethodType, UserActionButtonType } from '../types'

const mockUseConfig = vi.hoisted(() => vi.fn())
const mockUseStore = vi.hoisted(() => vi.fn())
const mockUseAvailableVarList = vi.hoisted(() => vi.fn())
const mockDeliveryMethod = vi.hoisted(() => vi.fn())
const mockFormContent = vi.hoisted(() => vi.fn())
const mockFormContentPreview = vi.hoisted(() => vi.fn())
const mockTimeoutInput = vi.hoisted(() => vi.fn())
const mockUserActionItem = vi.hoisted(() => vi.fn())

vi.mock('copy-to-clipboard', () => ({
  __esModule: true,
  default: vi.fn(),
}))

vi.mock('@langgenius/dify-ui/toast', () => ({
  toast: {
    success: vi.fn(),
  },
}))

vi.mock('@/app/components/base/action-button', () => ({
  __esModule: true,
  default: (props: {
    children: ReactNode
    onClick: () => void
  }) => (
    <button type="button" aria-label="action-button" onClick={props.onClick}>
      {props.children}
    </button>
  ),
}))

vi.mock('@/app/components/workflow/store', () => ({
  useStore: (selector: (state: { nodePanelWidth: number }) => unknown) => mockUseStore(selector),
}))

vi.mock('@/app/components/workflow/nodes/_base/hooks/use-available-var-list', () => ({
  __esModule: true,
  default: (...args: unknown[]) => mockUseAvailableVarList(...args),
}))

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

vi.mock('../components/delivery-method', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    onChange: (methods: HumanInputNodeType['delivery_methods']) => void
  }) => {
    mockDeliveryMethod(props)
    return (
      <button
        type="button"
        onClick={() => props.onChange([{
          id: 'dm-email',
          type: DeliveryMethodType.Email,
          enabled: true,
        }])}
      >
        {props.readonly ? 'delivery-method:readonly' : 'delivery-method:editable'}
      </button>
    )
  },
}))

vi.mock('../components/form-content', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    isExpand: boolean
    onChange: (value: string) => void
    onFormInputsChange: (value: HumanInputNodeType['inputs']) => void
    onFormInputItemRename: (oldName: string, newName: string) => void
    onFormInputItemRemove: (name: string) => void
  }) => {
    mockFormContent(props)
    return (
      <div>
        <div>{props.readonly ? 'form-content:readonly' : `form-content:${props.isExpand ? 'expanded' : 'collapsed'}`}</div>
        <button type="button" onClick={() => props.onChange('Updated content')}>
          change-form-content
        </button>
        <button
          type="button"
          onClick={() => props.onFormInputsChange([{
            type: InputVarType.paragraph,
            output_variable_name: 'email',
            default: {
              selector: [],
              type: 'constant',
              value: '',
            },
          }])}
        >
          change-form-inputs
        </button>
        <button type="button" onClick={() => props.onFormInputItemRename('name', 'email')}>
          rename-form-input
        </button>
        <button type="button" onClick={() => props.onFormInputItemRemove('name')}>
          remove-form-input
        </button>
      </div>
    )
  },
}))

vi.mock('../components/form-content-preview', () => ({
  __esModule: true,
  default: (props: {
    onClose: () => void
  }) => {
    mockFormContentPreview(props)
    return (
      <div>
        <div>form-preview</div>
        <button type="button" onClick={props.onClose}>
          close-preview
        </button>
      </div>
    )
  },
}))

vi.mock('../components/timeout', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    onChange: (value: { timeout: number, unit: 'hour' | 'day' }) => void
  }) => {
    mockTimeoutInput(props)
    return (
      <button
        type="button"
        onClick={() => props.onChange({ timeout: 8, unit: 'hour' })}
      >
        {props.readonly ? 'timeout:readonly' : 'timeout:editable'}
      </button>
    )
  },
}))

vi.mock('../components/user-action', () => ({
  __esModule: true,
  default: (props: {
    readonly: boolean
    data: HumanInputNodeType['user_actions'][number]
    onChange: (value: HumanInputNodeType['user_actions'][number]) => void
    onDelete: (id: string) => void
  }) => {
    mockUserActionItem(props)
    return (
      <div>
        <div>{`${props.data.id}:${props.readonly ? 'readonly' : 'editable'}`}</div>
        <button
          type="button"
          onClick={() => props.onChange({
            ...props.data,
            title: `${props.data.title} updated`,
          })}
        >
          {`change-action-${props.data.id}`}
        </button>
        <button type="button" onClick={() => props.onDelete(props.data.id)}>
          {`delete-action-${props.data.id}`}
        </button>
      </div>
    )
  },
}))

vi.mock('@/app/components/workflow/nodes/_base/components/output-vars', () => ({
  __esModule: true,
  default: (props: {
    children: ReactNode
    collapsed?: boolean
    onCollapse?: (collapsed: boolean) => void
  }) => (
    <div>
      <button type="button" onClick={() => props.onCollapse?.(!props.collapsed)}>
        toggle-output-vars
      </button>
      {props.children}
    </div>
  ),
  VarItem: ({ name, type, description }: { name: string, type: string, description: string }) => (
    <div>{`${name}:${type}:${description}`}</div>
  ),
}))

vi.mock('@remixicon/react', () => ({
  RiAddLine: () => <span>add-icon</span>,
  RiClipboardLine: () => <span>clipboard-icon</span>,
  RiCollapseDiagonalLine: () => <span>collapse-icon</span>,
  RiExpandDiagonalLine: () => <span>expand-icon</span>,
  RiEyeLine: () => <span>preview-icon</span>,
}))

const mockCopy = vi.mocked(copy)
const mockToastSuccess = vi.mocked(toast.success)

const createData = (overrides: Partial<HumanInputNodeType> = {}): HumanInputNodeType => ({
  title: 'Human Input',
  desc: '',
  type: BlockEnum.HumanInput,
  delivery_methods: [{
    id: 'dm-webapp',
    type: DeliveryMethodType.WebApp,
    enabled: true,
  }],
  form_content: 'Please review this request',
  inputs: [{
    type: InputVarType.paragraph,
    output_variable_name: 'review_result',
    default: {
      selector: [],
      type: 'constant',
      value: '',
    },
  }],
  user_actions: [{
    id: 'approve',
    title: 'Approve',
    button_style: UserActionButtonType.Primary,
  }],
  timeout: 3,
  timeout_unit: 'day',
  ...overrides,
})

const createConfigResult = (overrides: Partial<ReturnType<typeof useConfig>> = {}): ReturnType<typeof useConfig> => ({
  readOnly: false,
  inputs: createData(),
  handleDeliveryMethodChange: vi.fn(),
  handleUserActionAdd: vi.fn(),
  handleUserActionChange: vi.fn(),
  handleUserActionDelete: vi.fn(),
  handleTimeoutChange: vi.fn(),
  handleFormContentChange: vi.fn(),
  handleFormInputsChange: vi.fn(),
  handleFormInputItemRename: vi.fn(),
  handleFormInputItemRemove: vi.fn(),
  editorKey: 1,
  structuredOutputCollapsed: true,
  setStructuredOutputCollapsed: vi.fn(),
  ...overrides,
})

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

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

describe('human-input/panel', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockUseStore.mockImplementation(selector => selector({ nodePanelWidth: 480 }))
    mockUseAvailableVarList.mockImplementation((_id, options?: { filterVar?: (payload: { type: VarType }) => boolean }) => ({
      availableVars: [{
        variable: ['start', 'email'],
        type: VarType.string,
      }, {
        variable: ['code', 'result'],
        type: VarType.arrayString,
      }, {
        variable: ['start', 'files'],
        type: VarType.file,
      }].filter(variable => options?.filterVar ? options.filterVar({ type: variable.type } as never) : true),
      availableNodesWithParent: [{
        id: 'start-node',
        data: {
          title: 'Start',
          type: BlockEnum.Start,
        },
      }],
    }))
    mockUseConfig.mockReturnValue(createConfigResult())
  })

  it('renders editable controls, forwards updates, and toggles preview and output sections', async () => {
    const user = userEvent.setup()
    const config = createConfigResult()
    mockUseConfig.mockReturnValue(config)

    const { container } = renderPanel()

    expect(screen.getByRole('button', { name: 'delivery-method:editable' })).toBeInTheDocument()
    expect(screen.getByText('form-content:collapsed')).toBeInTheDocument()
    expect(screen.getByText('approve:editable')).toBeInTheDocument()
    expect(screen.getByText('review_result:string:Form input value')).toBeInTheDocument()
    expect(screen.getByText('__action_id:string:Action ID user triggered')).toBeInTheDocument()
    expect(screen.getByText('__rendered_content:string:Rendered content')).toBeInTheDocument()
    expect(mockDeliveryMethod).toHaveBeenCalledWith(expect.objectContaining({
      nodesOutputVars: [
        expect.objectContaining({ type: VarType.string }),
        expect.objectContaining({ type: VarType.arrayString }),
      ],
    }))

    await user.click(screen.getByRole('button', { name: 'delivery-method:editable' }))
    await user.click(screen.getByRole('button', { name: /workflow\.nodes\.humanInput\.formContent\.preview/ }))
    await user.click(screen.getByRole('button', { name: 'change-form-content' }))
    await user.click(screen.getByRole('button', { name: 'change-form-inputs' }))
    await user.click(screen.getByRole('button', { name: 'rename-form-input' }))
    await user.click(screen.getByRole('button', { name: 'remove-form-input' }))
    await user.click(screen.getByRole('button', { name: 'action-button' }))
    await user.click(screen.getByRole('button', { name: 'change-action-approve' }))
    await user.click(screen.getByRole('button', { name: 'delete-action-approve' }))
    await user.click(screen.getByRole('button', { name: 'timeout:editable' }))
    await user.click(screen.getByRole('button', { name: 'toggle-output-vars' }))
    await user.click(screen.getByRole('button', { name: 'close-preview' }))

    const iconContainers = container.querySelectorAll('div.flex.size-6.cursor-pointer')
    await user.click(iconContainers[0] as HTMLElement)
    await user.click(iconContainers[1] as HTMLElement)

    expect(config.handleDeliveryMethodChange).toHaveBeenCalledWith([{
      id: 'dm-email',
      type: DeliveryMethodType.Email,
      enabled: true,
    }])
    expect(config.handleFormContentChange).toHaveBeenCalledWith('Updated content')
    expect(config.handleFormInputsChange).toHaveBeenCalled()
    expect(config.handleFormInputItemRename).toHaveBeenCalledWith('name', 'email')
    expect(config.handleFormInputItemRemove).toHaveBeenCalledWith('name')
    expect(config.handleUserActionAdd).toHaveBeenCalledWith({
      id: 'action_2',
      title: 'Button Text 2',
      button_style: UserActionButtonType.Default,
    })
    expect(config.handleUserActionChange).toHaveBeenCalledWith(0, {
      id: 'approve',
      title: 'Approve updated',
      button_style: UserActionButtonType.Primary,
    })
    expect(config.handleUserActionDelete).toHaveBeenCalledWith('approve')
    expect(config.handleTimeoutChange).toHaveBeenCalledWith({ timeout: 8, unit: 'hour' })
    expect(config.setStructuredOutputCollapsed).toHaveBeenCalledWith(false)
    expect(mockCopy).toHaveBeenCalledWith('Please review this request')
    expect(mockToastSuccess).toHaveBeenCalledWith('common.actionMsg.copySuccessfully')
    expect(mockFormContentPreview).toHaveBeenCalled()
  })

  it('renders readonly and empty states without preview or add controls', () => {
    mockUseConfig.mockReturnValue(createConfigResult({
      readOnly: true,
      inputs: createData({
        user_actions: [],
      }),
      structuredOutputCollapsed: false,
    }))

    renderPanel()

    expect(screen.getByRole('button', { name: 'delivery-method:readonly' })).toBeInTheDocument()
    expect(screen.getByText('form-content:readonly')).toBeInTheDocument()
    expect(screen.getByText('workflow.nodes.humanInput.userActions.emptyTip')).toBeInTheDocument()
    expect(screen.queryByRole('button', { name: /workflow\.nodes\.humanInput\.formContent\.preview/ })).not.toBeInTheDocument()
    expect(screen.queryByRole('button', { name: 'action-button' })).not.toBeInTheDocument()
    expect(screen.getByRole('button', { name: 'timeout:readonly' })).toBeInTheDocument()
    expect(screen.queryByText('form-preview')).not.toBeInTheDocument()
  })

  it('renders file outputs with file-aware types', () => {
    mockUseConfig.mockReturnValue(createConfigResult({
      inputs: createData({
        inputs: [
          {
            type: InputVarType.singleFile,
            output_variable_name: 'attachment',
            allowed_file_extensions: [],
            allowed_file_types: [],
            allowed_file_upload_methods: [],
          },
          {
            type: InputVarType.multiFiles,
            output_variable_name: 'attachments',
            allowed_file_extensions: [],
            allowed_file_types: [],
            allowed_file_upload_methods: [],
            number_limits: 3,
          },
        ],
      }),
    }))

    renderPanel()

    expect(screen.getByText('attachment:file:Form input value')).toBeInTheDocument()
    expect(screen.getByText('attachments:array[file]:Form input value')).toBeInTheDocument()
  })
})
