import { DeleteOutlined, HolderOutlined, PlusOutlined } from '@ant-design/icons'
import { Button, Select, Table } from 'antd'
import type { ColumnsType } from 'antd/lib/table'
import React, {
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState
} from 'react'

import type { K8supgradeK8sInfo } from 'apiClient/services/devops/interface'

import { EmptyNodepoolsOptionsEditor } from './EmptyNodepoolsOptionsEditor'
import {
  STEPS_EDITOR_ACTION_COL_WIDTH,
  STEPS_EDITOR_CLUSTER_COL_WIDTH,
  STEPS_EDITOR_DRAG_COL_WIDTH,
  STEPS_EDITOR_INDEX_COL_WIDTH,
  STEPS_EDITOR_TABLE_SCROLL_X,
  STEPS_EDITOR_TARGET_COL_WIDTH,
  STEPS_EDITOR_VERSION_COL_WIDTH,
  type OptionsPanelAlign
} from './stepsEditorLayout'
import {
  getClusterIdsForStepTarget,
  getDefaultVersionForStep,
  getVersionOptionsForStep
} from './stepVersionOptions'
import {
  CLUSTER_ID_REQUIRED_TARGETS,
  EMPTY_NODEPOOLS_TARGET,
  EmptyNodepoolsStepOptions,
  UPGRADE_STEP_TARGET_OPTIONS,
  UpgradeOperationCrStep
} from './upgradeOperationCr'

import './StepsEditor.css'

const INDEX_COLUMN_WIDTH = STEPS_EDITOR_INDEX_COL_WIDTH
const TARGET_SELECT_WIDTH = '32ch'
const VERSION_SELECT_WIDTH = 88

type StepsChangeOptions = {
  recalculateDuration?: boolean
}

type Props = {
  steps: UpgradeOperationCrStep[]
  k8sInfo?: K8supgradeK8sInfo
  clusterIds: string[]
  disabled?: boolean
  onChange: (
    steps: UpgradeOperationCrStep[],
    options?: StepsChangeOptions
  ) => void
}

function createEmptyStep(): UpgradeOperationCrStep {
  return {
    target: 'controlplane',
    version: ''
  }
}

export const StepsEditor: React.FC<Props> = ({
  steps,
  k8sInfo,
  clusterIds,
  disabled,
  onChange
}) => {
  const [dragIndex, setDragIndex] = useState<number | null>(null)
  const [dropIndex, setDropIndex] = useState<number | null>(null)
  const tableWrapperRef = useRef<HTMLDivElement>(null)
  const [optionsPanelAlign, setOptionsPanelAlign] = useState<OptionsPanelAlign>(
    {
      left: STEPS_EDITOR_DRAG_COL_WIDTH + STEPS_EDITOR_INDEX_COL_WIDTH,
      width: STEPS_EDITOR_TARGET_COL_WIDTH
    }
  )

  const emptyNodepoolRowKeys = useMemo(
    () =>
      steps
        .map((step, index) =>
          step.target === EMPTY_NODEPOOLS_TARGET ? index : -1
        )
        .filter((index) => index >= 0),
    [steps]
  )

  const measureOptionsPanelAlign = useCallback(() => {
    const wrapper = tableWrapperRef.current
    if (!wrapper || emptyNodepoolRowKeys.length === 0) {
      return
    }

    const targetCell = wrapper.querySelector<HTMLElement>(
      'tbody td.steps-editor-target-col'
    )
    const expandedContainer =
      wrapper.querySelector<HTMLElement>('.ant-table-expanded-row-fixed') ||
      wrapper.querySelector<HTMLElement>('.ant-table-expanded-row > td')

    if (!targetCell || !expandedContainer) {
      return
    }

    const targetRect = targetCell.getBoundingClientRect()
    const expandedRect = expandedContainer.getBoundingClientRect()
    const nextLeft = targetRect.left - expandedRect.left
    const nextWidth = targetRect.width

    setOptionsPanelAlign((prev) => {
      if (
        Math.abs(prev.left - nextLeft) < 0.5 &&
        Math.abs(prev.width - nextWidth) < 0.5
      ) {
        return prev
      }
      return { left: nextLeft, width: nextWidth }
    })
  }, [emptyNodepoolRowKeys])

  useLayoutEffect(() => {
    const frameId = requestAnimationFrame(() => {
      measureOptionsPanelAlign()
    })
    return () => cancelAnimationFrame(frameId)
  }, [measureOptionsPanelAlign, steps])

  useEffect(() => {
    const wrapper = tableWrapperRef.current
    if (!wrapper || typeof ResizeObserver === 'undefined') {
      return
    }

    const observer = new ResizeObserver(() => {
      measureOptionsPanelAlign()
    })
    observer.observe(wrapper)
    return () => observer.disconnect()
  }, [measureOptionsPanelAlign])

  const targetOptions = useMemo(() => {
    const known = UPGRADE_STEP_TARGET_OPTIONS.map((value) => ({
      label: value,
      value
    }))
    const extraTargets = Array.from(
      new Set(
        steps
          .map((step) => step.target?.trim())
          .filter(
            (target): target is string =>
              !!target &&
              !UPGRADE_STEP_TARGET_OPTIONS.includes(
                target as (typeof UPGRADE_STEP_TARGET_OPTIONS)[number]
              )
          )
      )
    ).map((value) => ({ label: value, value }))

    return [...known, ...extraTargets]
  }, [steps])

  const getClusterOptionsForStep = (target: string) => {
    const allowedIds = new Set(getClusterIdsForStepTarget(target, k8sInfo))
    return clusterIds
      .filter((id) => allowedIds.has(id))
      .map((id) => ({ label: id, value: id }))
  }

  const getPreviousStepVersion = (index: number) =>
    index > 0 ? steps[index - 1]?.version : undefined

  const updateStep = (
    index: number,
    patch: Partial<UpgradeOperationCrStep>
  ) => {
    const previousStepVersion = getPreviousStepVersion(index)
    const next = steps.map((step, i) => {
      if (i !== index) return step
      const merged = { ...step, ...patch }

      if (CLUSTER_ID_REQUIRED_TARGETS.has(merged.target)) {
        const allowedClusterIds = getClusterIdsForStepTarget(
          merged.target,
          k8sInfo
        )
        if (
          merged.cluster_id &&
          !allowedClusterIds.includes(merged.cluster_id)
        ) {
          merged.cluster_id = undefined
          merged.version = ''
        }
      }

      const versionOptions = getVersionOptionsForStep(
        merged.target,
        k8sInfo,
        merged.cluster_id,
        previousStepVersion
      )

      if (
        !merged.version &&
        (merged.target === 'addons' || merged.target === EMPTY_NODEPOOLS_TARGET)
      ) {
        merged.version = getDefaultVersionForStep(
          merged.target,
          k8sInfo,
          merged.cluster_id,
          previousStepVersion
        )
      }

      if (merged.version && !versionOptions.includes(merged.version)) {
        merged.version = versionOptions[0] || ''
      }
      return merged
    })
    onChange(next, { recalculateDuration: true })
  }

  const updateEmptyNodepoolsOptions = (
    index: number,
    options: EmptyNodepoolsStepOptions | undefined
  ) => {
    updateStep(index, { options })
  }

  const removeStep = (index: number) => {
    onChange(
      steps.filter((_, i) => i !== index),
      { recalculateDuration: true }
    )
  }

  const reorderSteps = (fromIndex: number, toIndex: number) => {
    if (fromIndex === toIndex) return
    const next = [...steps]
    const [moved] = next.splice(fromIndex, 1)
    next.splice(toIndex, 0, moved)
    onChange(next, { recalculateDuration: false })
  }

  const getRowStyle = (index?: number): React.CSSProperties => {
    if (index == null || dragIndex == null) {
      return {}
    }
    if (dragIndex === index) {
      return {
        background: '#e6f7ff',
        boxShadow: '0 4px 12px rgba(24, 144, 255, 0.28)'
      }
    }
    if (dropIndex === index) {
      return {
        background: '#f0f5ff',
        boxShadow: 'inset 0 3px 0 #1890ff'
      }
    }
    return { opacity: 0.72 }
  }

  const columns: ColumnsType<UpgradeOperationCrStep & { rowId: number }> = [
    {
      title: '',
      width: STEPS_EDITOR_DRAG_COL_WIDTH,
      className: 'steps-editor-drag-col',
      render: (_, __, index) => (
        <HolderOutlined
          draggable={!disabled}
          onDragStart={(event) => {
            event.stopPropagation()
            setDragIndex(index)
            setDropIndex(index)
          }}
          onDragEnd={() => {
            setDragIndex(null)
            setDropIndex(null)
          }}
          style={{
            cursor: disabled ? 'not-allowed' : 'grab',
            color: dragIndex === index ? '#1890ff' : '#8c8c8c',
            fontSize: 16
          }}
        />
      )
    },
    {
      title: '#',
      width: INDEX_COLUMN_WIDTH,
      align: 'right',
      className: 'steps-editor-index-col',
      render: (_, __, index) => index + 1
    },
    {
      title: 'Target',
      dataIndex: 'target',
      width: STEPS_EDITOR_TARGET_COL_WIDTH,
      className: 'steps-editor-target-col',
      render: (_, record, index) => (
        <Select
          style={{ width: TARGET_SELECT_WIDTH, maxWidth: '100%' }}
          dropdownMatchSelectWidth={false}
          dropdownStyle={{ minWidth: TARGET_SELECT_WIDTH }}
          value={record.target}
          disabled={disabled}
          options={targetOptions}
          onChange={(value) => {
            const patch: Partial<UpgradeOperationCrStep> = {
              target: value,
              version: ''
            }
            if (!CLUSTER_ID_REQUIRED_TARGETS.has(value)) {
              patch.cluster_id = undefined
            }
            if (value !== EMPTY_NODEPOOLS_TARGET) {
              patch.options = undefined
            }
            updateStep(index, patch)
          }}
        />
      )
    },
    {
      title: 'Version',
      dataIndex: 'version',
      width: STEPS_EDITOR_VERSION_COL_WIDTH,
      render: (_, record, index) => {
        const versionOptions = getVersionOptionsForStep(
          record.target,
          k8sInfo,
          record.cluster_id,
          getPreviousStepVersion(index)
        ).map((version) => ({ label: version, value: version }))

        return (
          <Select
            style={{ width: VERSION_SELECT_WIDTH }}
            showSearch
            value={record.version || undefined}
            disabled={disabled || versionOptions.length === 0}
            options={versionOptions}
            placeholder="version"
            onChange={(value) => updateStep(index, { version: value })}
          />
        )
      }
    },
    {
      title: 'Cluster ID',
      dataIndex: 'cluster_id',
      width: STEPS_EDITOR_CLUSTER_COL_WIDTH,
      render: (_, record, index) => {
        if (!CLUSTER_ID_REQUIRED_TARGETS.has(record.target)) {
          return '-'
        }

        const clusterOptions = getClusterOptionsForStep(record.target)
        const clusterDisabled = disabled || clusterOptions.length === 0

        return (
          <Select
            style={{ width: '100%' }}
            showSearch
            allowClear
            value={
              clusterOptions.some(
                (option) => option.value === record.cluster_id
              )
                ? record.cluster_id
                : undefined
            }
            disabled={clusterDisabled}
            options={clusterOptions}
            placeholder={
              clusterOptions.length === 0 ? 'no cluster' : 'cluster id'
            }
            onChange={(value) =>
              updateStep(index, {
                cluster_id: value || undefined,
                version: ''
              })
            }
          />
        )
      }
    },
    {
      title: '',
      width: STEPS_EDITOR_ACTION_COL_WIDTH,
      render: (_, __, index) => (
        <Button
          size="small"
          danger
          icon={<DeleteOutlined />}
          disabled={disabled || steps.length <= 1}
          onClick={() => removeStep(index)}
        />
      )
    }
  ]

  return (
    <div ref={tableWrapperRef}>
      <Table
        className="steps-editor-table"
        size="small"
        pagination={false}
        rowKey="rowId"
        columns={columns}
        dataSource={steps.map((step, index) => ({ ...step, rowId: index }))}
        scroll={{ x: STEPS_EDITOR_TABLE_SCROLL_X }}
        tableLayout="fixed"
        expandable={{
          expandedRowKeys: emptyNodepoolRowKeys,
          expandIconColumnIndex: -1,
          expandIcon: () => null,
          rowExpandable: (record) => record.target === EMPTY_NODEPOOLS_TARGET,
          expandedRowRender: (record) => {
            const index = record.rowId
            return (
              <EmptyNodepoolsOptionsEditor
                key={`${index}-${record.target}`}
                options={record.options}
                disabled={disabled}
                alignLeft={optionsPanelAlign.left}
                alignWidth={optionsPanelAlign.width}
                defaultExpanded={
                  !!(record.options?.delay || record.options?.delete != null)
                }
                onChange={(options) =>
                  updateEmptyNodepoolsOptions(index, options)
                }
              />
            )
          }
        }}
        onRow={(_, index) => ({
          onDragOver: (event) => {
            event.preventDefault()
            if (index != null) {
              setDropIndex(index)
            }
          },
          onDragEnter: (event) => {
            event.preventDefault()
            if (index != null) {
              setDropIndex(index)
            }
          },
          onDrop: (event) => {
            event.preventDefault()
            if (dragIndex == null || index == null) return
            reorderSteps(dragIndex, index)
            setDragIndex(null)
            setDropIndex(null)
          },
          style: getRowStyle(index)
        })}
      />
      <Button
        type="dashed"
        icon={<PlusOutlined />}
        disabled={disabled}
        style={{ marginTop: 8 }}
        onClick={() =>
          onChange([...steps, createEmptyStep()], { recalculateDuration: true })
        }
      >
        Add Step
      </Button>
    </div>
  )
}
