import {
  BellOutlined,
  CopyOutlined,
  DownloadOutlined,
  ReloadOutlined,
  RightOutlined
} from '@ant-design/icons'
import { useSessionStorageState } from 'ahooks'
import {
  Alert,
  Button,
  Card,
  Collapse,
  Dropdown,
  Divider,
  Form,
  Input,
  Row,
  Col,
  Menu,
  message,
  Select,
  Space,
  Table,
  Tag,
  Tooltip,
  Typography
} from 'antd'
import type { ColumnsType, TablePaginationConfig } from 'antd/lib/table'
import moment from 'moment'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useHistory, useLocation } from 'react-router-dom'

import {
  getK8sUpgradeAdmin,
  getK8sUpgradeTask,
  getMaintenance,
  listK8sUpgradeTasks
} from 'apiClient/services/devops'
import { ListK8sUpgradeTasksCloudProviderItem } from 'apiClient/services/devops/interface'
import type {
  K8supgradeK8sCluster,
  K8supgradeGetTaskResp,
  K8supgradeListTasksResp,
  K8supgradeTask
} from 'apiClient/services/devops/interface'
import { ListToolBar } from 'components/Table'

import { ExpandedRowDetail } from './ExpandedRowDetail'
import { NotifyUpgradeModal } from './NotifyUpgradeModal'
import type { ClusterSummaryRow, FiltersState, MaintenanceCache } from './types'
import { UpgradeScheduleHeatmapTrigger } from './UpgradeScheduleHeatmap'
import {
  clusterMatchesQuery,
  countTotalClusters,
  buildClusterSummaryCsvRow,
  CLUSTER_SUMMARY_CSV_HEADERS,
  CLUSTER_SUMMARY_DEFAULT_PAGINATION,
  CLUSTER_SUMMARY_DEFAULT_SORT,
  buildClusterSummarySearch,
  clusterSummaryStateFromSearchParams,
  filtersToFormValues,
  getDefaultClusterSummaryUrlState,
  hasClusterSummaryUrlParams,
  isDefaultClusterSummarySort,
  K8S_UPGRADE_TASK_STATUS_OPTIONS,
  UPGRADE_OPERATION_STATUS_OPTIONS,
  formatUpgradeDuration,
  formatVersion,
  getK8sInfoSnapshot,
  parseUpgradeOperationCr,
  resolveUpgradeDurationMs,
  toCsvValue
} from './utils'

type SortOrder = 'ascend' | 'descend' | undefined
const { Panel } = Collapse

function uniq<T>(arr: T[]) {
  return Array.from(new Set(arr))
}

function shouldSkipNotFoundLike(e: any) {
  const errcode = e?.response?.data?.errcode
  const errmsg =
    e?.response?.data?.errmsg || e?.response?.data?.message || e?.message || ''
  if (errcode !== 'INTERNAL') return false
  return String(errmsg).toLowerCase().includes('not found')
}

const NAV_LINK_COLOR = '#1890ff'

function renderNavLabelWithCopy(
  onCopy: (text: string) => void,
  options: {
    label: string
    to?: string
    copyValue?: string
    maxWidth?: number
    copyTooltip: string
    openInNewTab?: boolean
  }
) {
  const display = options.label || '-'
  const canCopy = display !== '-'
  const copyVal = options.copyValue ?? display
  const maxWidth = options.maxWidth ?? 190

  const nameNode = options.to ? (
    <Link
      to={options.to}
      title={display}
      target={options.openInNewTab ? '_blank' : undefined}
      rel={options.openInNewTab ? 'noopener noreferrer' : undefined}
      style={{
        color: NAV_LINK_COLOR,
        maxWidth,
        display: 'inline-block',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        verticalAlign: 'bottom'
      }}
    >
      {display}
    </Link>
  ) : (
    <Typography.Text style={{ maxWidth }} ellipsis={{ tooltip: display }}>
      {display}
    </Typography.Text>
  )

  return (
    <Space size={6}>
      {nameNode}
      <Tooltip title={options.copyTooltip}>
        <Button
          size="small"
          type="text"
          icon={<CopyOutlined />}
          disabled={!canCopy}
          onClick={(e) => {
            e.stopPropagation()
            onCopy(copyVal)
          }}
        />
      </Tooltip>
    </Space>
  )
}

function normalizeLike(v?: string) {
  return (v || '').trim().toLowerCase()
}

function hasScheduleTime(row: { maintenanceScheduleTime?: number }): boolean {
  return (
    row.maintenanceScheduleTime != null &&
    row.maintenanceScheduleTime !== Number.POSITIVE_INFINITY
  )
}

function compareDefaultClusterSummaryRows(a: any, b: any): number {
  const aHas = hasScheduleTime(a)
  const bHas = hasScheduleTime(b)

  if (aHas && bHas) {
    const diff = a.maintenanceScheduleTime - b.maintenanceScheduleTime
    if (diff !== 0) return diff
  } else if (aHas !== bHas) {
    return aHas ? -1 : 1
  }

  if (a.taskId !== b.taskId) return a.taskId - b.taskId

  const businessRegionCmp = String(a.businessRegion || '').localeCompare(
    String(b.businessRegion || '')
  )
  if (businessRegionCmp !== 0) return businessRegionCmp

  const aTenant = String(a.tenantName || a.tenantId || '')
  const bTenant = String(b.tenantName || b.tenantId || '')
  const tenantCmp = aTenant.localeCompare(bTenant)
  if (tenantCmp !== 0) return tenantCmp

  return String(a.projectId || '').localeCompare(String(b.projectId || ''))
}

function formatScheduleTimeLocal(iso?: string) {
  if (!iso) return '-'
  const m = moment(iso)
  if (!m.isValid()) return iso
  return m.local().format('YYYY-MM-DD HH:mm:ss')
}

function getDistanceTextAndColor(iso?: string) {
  if (!iso) return { text: '-', color: undefined as string | undefined }
  const m = moment(iso)
  if (!m.isValid()) return { text: '-', color: undefined as string | undefined }
  const diffMs = m.valueOf() - Date.now()
  const absMs = Math.abs(diffMs)
  const dur = moment.duration(absMs)
  const days = Math.floor(dur.asDays())
  const hours = dur.hours()
  const minutes = dur.minutes()

  let text = ''
  if (absMs < 60_000) text = diffMs < 0 ? '刚刚过期' : '即将开始'
  else if (absMs < 3_600_000) text = `${minutes} 分钟`
  else if (absMs < 86_400_000) text = `${hours} 小时 ${minutes} 分钟`
  else text = hours > 0 ? `${days} 天 ${hours} 小时` : `${days} 天`

  if (diffMs < 0) return { text: `已过期 ${text}`, color: undefined }
  if (diffMs < 86_400_000) return { text: `${text} 后`, color: 'red' }
  if (diffMs < 3 * 86_400_000) return { text: `${text} 后`, color: 'orange' }
  return { text: `${text} 后`, color: 'green' }
}

function getInitialUrlState() {
  const params = new URLSearchParams(window.location.search)
  if (!hasClusterSummaryUrlParams(params)) return null
  return clusterSummaryStateFromSearchParams(params)
}

const initialUrlState = getInitialUrlState()

function getInitialFilters(): FiltersState {
  if (initialUrlState?.filters) return initialUrlState.filters
  try {
    const raw = sessionStorage.getItem('k8s_upgrade_cluster_summary_filters')
    if (raw) return JSON.parse(raw) as FiltersState
  } catch {
    // ignore invalid session storage payload
  }
  return { taskIds: [] }
}

export default function K8sUpgradeClusterSummary() {
  const location = useLocation()
  const history = useHistory()
  const [canSendUpgradeNotification, setCanSendUpgradeNotification] =
    useState(false)
  const [form] = Form.useForm<FiltersState>()

  const [loading, setLoading] = useState(false)
  const [loadError, setLoadError] = useState<string>('')
  const [progressText, setProgressText] = useState<string>('')
  const [taskOptionsLoading, setTaskOptionsLoading] = useState(false)
  const [tasks, setTasks] = useState<K8supgradeTask[]>([])
  const [rows, setRows] = useState<ClusterSummaryRow[]>([])
  const [maintenanceCache, setMaintenanceCache] = useState<MaintenanceCache>({})
  const taskDetailCacheRef = useRef<Map<number, K8supgradeGetTaskResp>>(
    new Map()
  )
  const selectedTaskIdsRef = useRef<number[]>([])
  const lastAppliedSearchRef = useRef<string | null>(null)
  const resettingFiltersRef = useRef(false)

  const [filters, setFilters] = useState<FiltersState>(getInitialFilters)

  const [autoRefreshEnabled, setAutoRefreshEnabled] =
    useSessionStorageState<boolean>(
      'k8s_upgrade_cluster_summary_auto_refresh',
      {
        defaultValue: false
      }
    )
  const [autoRefreshIntervalSec, setAutoRefreshIntervalSec] =
    useSessionStorageState<number>(
      'k8s_upgrade_cluster_summary_auto_refresh_interval_sec',
      { defaultValue: 60 }
    )

  const [autoRefreshNextAt, setAutoRefreshNextAt] = useState<number | null>(
    null
  )
  const [autoRefreshPercent, setAutoRefreshPercent] = useState<number>(0)
  const autoRefreshNextAtRef = useRef<number | null>(null)

  const [sortState, setSortState] = useState<{
    field?: string
    order?: SortOrder
  }>(initialUrlState?.sort ?? { ...CLUSTER_SUMMARY_DEFAULT_SORT })

  const [pagination, setPagination] = useState<TablePaginationConfig>({
    current:
      initialUrlState?.pagination.current ??
      CLUSTER_SUMMARY_DEFAULT_PAGINATION.current,
    pageSize:
      initialUrlState?.pagination.pageSize ??
      CLUSTER_SUMMARY_DEFAULT_PAGINATION.pageSize,
    showSizeChanger: true
  })

  const mountedRef = useRef(true)

  useEffect(() => {
    mountedRef.current = true
    return () => {
      mountedRef.current = false
    }
  }, [])

  useEffect(() => {
    let cancelled = false

    const fetchK8sUpgradeAdmin = async () => {
      try {
        const resp = await getK8sUpgradeAdmin()
        if (cancelled) return
        setCanSendUpgradeNotification(!!resp.data.is_admin)
      } catch {
        if (!cancelled) setCanSendUpgradeNotification(false)
      }
    }

    fetchK8sUpgradeAdmin()

    return () => {
      cancelled = true
    }
  }, [])

  const fetchTaskOptions = useCallback(async () => {
    if (taskOptionsLoading) return
    setTaskOptionsLoading(true)
    setLoadError('')
    try {
      const perPage = 50
      let page = 1
      let all: K8supgradeTask[] = []
      // eslint-disable-next-line no-constant-condition
      while (true) {
        const resp = await listK8sUpgradeTasks({ page, per_page: perPage })
        const data = resp.data as K8supgradeListTasksResp
        const items = data.items || []
        all = all.concat(items)
        const total = data.total ?? 0
        if (items.length < perPage) break
        if (total > 0 && all.length >= total) break
        page += 1
        if (page > 200) break
      }

      if (all.length === 0) {
        const resp = await listK8sUpgradeTasks()
        const data = resp.data as K8supgradeListTasksResp
        all = data.items || []
      }

      if (!mountedRef.current) return
      setTasks(all)
    } catch (e: any) {
      if (!mountedRef.current) return
      const msg =
        e?.response?.data?.message ||
        e?.response?.data?.errmsg ||
        e?.message ||
        '加载 Task 列表失败'
      setLoadError(msg)
    } finally {
      if (mountedRef.current) setTaskOptionsLoading(false)
    }
  }, [taskOptionsLoading])

  const [notifyTaskId, setNotifyTaskId] = useState<number | null>(null)
  const [notifyLoading, setNotifyLoading] = useState(false)

  const buildRowsFromTaskDetails = useCallback(
    (details: K8supgradeGetTaskResp[]) => {
      const flattened: ClusterSummaryRow[] = []
      for (const task of details) {
        const clusters = (task.k8s_cluster_list || []) as K8supgradeK8sCluster[]
        if (clusters.length === 0) continue
        for (const c of clusters) {
          const snapshot = getK8sInfoSnapshot(c)
          const parsedCr = parseUpgradeOperationCr(c.upgrade_operation_cr)
          flattened.push({
            key: `${task.id}-${c.name || ''}-${c.id || ''}`,
            taskId: task.id!,
            taskName: task.name || String(task.id),
            taskStatus: task.status || '-',
            cloudProvider: c.cloud_provider || task.cloud_provider,
            eosVersion: task.version_to_be_upgraded,
            deadline: task.deadline,
            maxRestartTimes: task.max_restart_times,

            k8sName: c.name || snapshot?.k8s_name || '-',
            region: c.region || snapshot?.region,
            businessRegion: c.business_region || snapshot?.business_region,
            desiredVersion: c.desired_version,

            tenantName: c.tenant_name || snapshot?.tenant_name,
            tenantKind: c.tenant_kind || snapshot?.tenant_kind,
            tenantId: snapshot?.tenant_id,
            salesOwnerEmail: c.sales_owner_email,
            tamName: c.tam_name,
            projectId: snapshot?.project_id,

            controlPlaneVersion: formatVersion(snapshot?.control_plane_version),
            totalClusterCount: countTotalClusters(snapshot),
            upgradeClusterCount: parsedCr.upgradeCount,
            upgradeDurationMs: resolveUpgradeDurationMs(
              c.upgrade_operation_cr,
              snapshot,
              c.cloud_provider || task.cloud_provider
            ),
            upgradeDurationLabel: formatUpgradeDuration(
              c.upgrade_operation_cr,
              snapshot,
              c.cloud_provider || task.cloud_provider
            ),

            k8sSnapshot: snapshot,
            upgradeOperationCr: c.upgrade_operation_cr,

            maintenanceWindowId: c.maintenance_window_id,
            upgradeOperationStatus: c.upgrade_operation_status,
            isExcluded: c.is_excluded,
            disableAutoGeneration: c.disable_auto_generation
          })
        }
      }
      return flattened
    },
    []
  )

  const fetchSelectedTaskDetails = useCallback(async (taskIds: number[]) => {
    setLoading(true)
    setLoadError('')
    setProgressText('')
    try {
      const ids = uniq(taskIds).filter((v) => typeof v === 'number')
      if (ids.length === 0) {
        setRows([])
        setMaintenanceCache({})
        return
      }

      // only fetch missing ones, reuse cache for already-loaded tasks
      const cache = taskDetailCacheRef.current
      const missing = ids.filter((id) => !cache.has(id))
      const details: K8supgradeGetTaskResp[] = []
      for (const id of ids) {
        const cached = cache.get(id)
        if (cached) details.push(cached)
      }

      const concurrency = 6
      let done = 0

      const runOne = async (id: number) => {
        const resp = await getK8sUpgradeTask(String(id), {
          with_excluded_k8s_clusters: false
        })
        return resp.data
      }

      const queue = [...missing]
      const workers = Array.from({
        length: Math.min(concurrency, queue.length)
      }).map(async () => {
        while (queue.length) {
          const id = queue.shift()
          if (id == null) return
          try {
            const d = await runOne(id)
            cache.set(id, d)
            details.push(d)
          } catch (e) {
            // ignore single task failure, keep going
          } finally {
            done += 1
            if (mountedRef.current) {
              setProgressText(
                `正在加载任务详情：${done}/${missing.length || 0}`
              )
            }
          }
        }
      })
      await Promise.all(workers)

      setRows(buildRowsFromTaskDetails(details))
      setPagination((p) => ({ ...p, current: 1 }))
    } catch (e: any) {
      if (!mountedRef.current) return
      const msg =
        e?.response?.data?.message ||
        e?.response?.data?.errmsg ||
        e?.message ||
        '加载失败'
      setLoadError(msg)
    } finally {
      if (mountedRef.current) setLoading(false)
      if (mountedRef.current) setProgressText('')
    }
  }, [])

  const refreshSelectedTaskDetails = useCallback(
    async (taskIds: number[]) => {
      const ids = uniq(taskIds).filter((v) => typeof v === 'number')
      if (ids.length === 0) return

      // force re-fetch: drop cache entries for selected tasks
      const cache = taskDetailCacheRef.current
      for (const id of ids) cache.delete(id)

      // also refresh maintenance cache (schedule time may change)
      setMaintenanceCache({})
      await fetchSelectedTaskDetails(ids)
    },
    [fetchSelectedTaskDetails]
  )

  const resetAutoRefreshCountdown = useCallback(() => {
    const enabled = !!autoRefreshEnabled && !!autoRefreshIntervalSec
    if (!enabled) {
      autoRefreshNextAtRef.current = null
      setAutoRefreshNextAt(null)
      setAutoRefreshPercent(0)
      return
    }

    const intervalMs = Math.max(5, autoRefreshIntervalSec) * 1000
    const nextAt = Date.now() + intervalMs
    autoRefreshNextAtRef.current = nextAt
    setAutoRefreshNextAt(nextAt)
    setAutoRefreshPercent(0)
  }, [autoRefreshEnabled, autoRefreshIntervalSec])

  useEffect(() => {
    const ids = filters.taskIds || []
    if (!ids.length) {
      setAutoRefreshNextAt(null)
      setAutoRefreshPercent(0)
      autoRefreshNextAtRef.current = null
      return
    }
    if (!autoRefreshEnabled || !autoRefreshIntervalSec) {
      setAutoRefreshNextAt(null)
      setAutoRefreshPercent(0)
      autoRefreshNextAtRef.current = null
      return
    }

    const intervalMs = Math.max(5, autoRefreshIntervalSec) * 1000
    const initialNextAt = Date.now() + intervalMs
    autoRefreshNextAtRef.current = initialNextAt
    setAutoRefreshNextAt(initialNextAt)
    setAutoRefreshPercent(0)

    const t = window.setInterval(() => {
      const now = Date.now()
      const nextAt = autoRefreshNextAtRef.current ?? initialNextAt
      const remaining = Math.max(0, nextAt - now)
      const pct = Math.min(100, Math.max(0, (1 - remaining / intervalMs) * 100))
      setAutoRefreshPercent(pct)

      if (now >= nextAt) {
        // avoid overlapping refresh; if busy, schedule next tick from now
        if (!loading) refreshSelectedTaskDetails(ids)
        const newNextAt = now + intervalMs
        autoRefreshNextAtRef.current = newNextAt
        setAutoRefreshNextAt(newNextAt)
        setAutoRefreshPercent(0)
      }
    }, 200)

    return () => window.clearInterval(t)
  }, [
    autoRefreshEnabled,
    autoRefreshIntervalSec,
    filters.taskIds,
    loading,
    refreshSelectedTaskDetails,
    resetAutoRefreshCountdown
  ])

  const autoRefreshLabel = useMemo(() => {
    const sec = autoRefreshEnabled ? autoRefreshIntervalSec || 0 : 0
    if (!sec) return 'Off'
    if (sec < 60) return `${sec}s`
    if (sec % 3600 === 0) return `${sec / 3600}h`
    if (sec % 60 === 0) return `${sec / 60}m`
    return `${sec}s`
  }, [autoRefreshEnabled, autoRefreshIntervalSec])

  const autoRefreshMenu = useMemo(() => {
    const options: Array<{ label: string; value: number }> = [
      { label: 'Off', value: 0 },
      { label: '5s', value: 5 },
      { label: '10s', value: 10 },
      { label: '30s', value: 30 },
      { label: '1m', value: 60 },
      { label: '5m', value: 300 },
      { label: '15m', value: 900 },
      { label: '30m', value: 1800 },
      { label: '1h', value: 3600 },
      { label: '2h', value: 7200 },
      { label: '1d', value: 86400 }
    ]

    return (
      <Menu
        selectedKeys={[
          String(autoRefreshEnabled ? autoRefreshIntervalSec || 0 : 0)
        ]}
        onClick={({ key }) => {
          const v = Number(key)
          if (!v) {
            setAutoRefreshEnabled(false)
            setAutoRefreshIntervalSec(0)
            resetAutoRefreshCountdown()
            return
          }
          setAutoRefreshEnabled(true)
          setAutoRefreshIntervalSec(v)
          resetAutoRefreshCountdown()
        }}
      >
        {options.map((o) => (
          <Menu.Item key={String(o.value)}>{o.label}</Menu.Item>
        ))}
      </Menu>
    )
  }, [
    autoRefreshEnabled,
    autoRefreshIntervalSec,
    setAutoRefreshEnabled,
    setAutoRefreshIntervalSec,
    resetAutoRefreshCountdown
  ])

  // lazy load: only when user selects tasks
  useEffect(() => {
    const ids = filters.taskIds || []
    if (!ids.length) {
      selectedTaskIdsRef.current = []
      setRows([])
      setMaintenanceCache({})
      setLoadError('')
      setProgressText('')
      return
    }

    // incremental: remove unselected rows; only fetch newly added tasks
    const prev = selectedTaskIdsRef.current
    const next = ids
    const prevSet = new Set(prev)
    const nextSet = new Set(next)
    const removed = prev.filter((id) => !nextSet.has(id))
    const added = next.filter((id) => !prevSet.has(id))
    selectedTaskIdsRef.current = next

    if (removed.length) {
      setRows((prevRows) => prevRows.filter((r) => !removed.includes(r.taskId)))
    }
    if (added.length) {
      fetchSelectedTaskDetails(next)
    }
  }, [fetchSelectedTaskDetails, filters.taskIds])

  const allTaskIdOptions = useMemo(
    () =>
      tasks
        .filter((t) => t.id != null)
        .map((t) => ({
          label: `${t.id} ${t.name || ''}`.trim(),
          value: t.id!
        })),
    [tasks]
  )

  const allRegionOptions = useMemo(() => {
    return uniq(rows.map((r) => r.region).filter(Boolean) as string[]).sort()
  }, [rows])

  const allTenantOptions = useMemo(() => {
    const options = uniq(
      rows
        .flatMap((r) => [r.tenantName, r.tenantId])
        .filter(Boolean) as string[]
    ).sort()
    return options.map((v) => ({ label: v, value: v }))
  }, [rows])

  const allSalesOwnerOptions = useMemo(() => {
    const options = uniq(
      rows.map((r) => r.salesOwnerEmail).filter(Boolean) as string[]
    ).sort()
    return options.map((v) => ({ label: v, value: v }))
  }, [rows])

  const allTamOptions = useMemo(() => {
    const options = uniq(
      rows.map((r) => r.tamName).filter(Boolean) as string[]
    ).sort()
    return options.map((v) => ({ label: v, value: v }))
  }, [rows])

  const allBusinessRegionOptions = useMemo(() => {
    return uniq(
      rows.map((r) => r.businessRegion).filter(Boolean) as string[]
    ).sort()
  }, [rows])

  const allProjectOptions = useMemo(() => {
    return uniq(rows.map((r) => r.projectId).filter(Boolean) as string[])
      .sort()
      .map((v) => ({ label: v, value: v }))
  }, [rows])

  const selectedTaskIds = useMemo(
    () => uniq(filters.taskIds || []),
    [filters.taskIds]
  )

  const openNotifyModal = useCallback((taskId: number) => {
    setNotifyTaskId(taskId)
  }, [])

  const noticeMenu = useMemo(() => {
    if (selectedTaskIds.length <= 1) return null
    return (
      <Menu
        onClick={({ key }) => {
          openNotifyModal(Number(key))
        }}
      >
        {selectedTaskIds.map((taskId) => (
          <Menu.Item key={String(taskId)}>Task {taskId}</Menu.Item>
        ))}
      </Menu>
    )
  }, [openNotifyModal, selectedTaskIds])

  const applyFilters = useCallback((newFilters: FiltersState) => {
    setFilters((prev) => ({ ...prev, ...newFilters }))
  }, [])

  const resetAllFilters = useCallback(() => {
    const defaults = getDefaultClusterSummaryUrlState()
    resettingFiltersRef.current = true

    lastAppliedSearchRef.current = ''
    history.replace({
      pathname: location.pathname,
      search: ''
    })

    form.setFieldsValue(filtersToFormValues(defaults.filters))
    setFilters({ ...defaults.filters })
    setSortState({ ...defaults.sort })
    setPagination((p) => ({
      ...p,
      current: defaults.pagination.current,
      pageSize: defaults.pagination.pageSize
    }))
    selectedTaskIdsRef.current = []
    setRows([])
    setMaintenanceCache({})

    try {
      sessionStorage.setItem(
        'k8s_upgrade_cluster_summary_filters',
        JSON.stringify(defaults.filters)
      )
    } catch {
      // ignore quota / privacy mode errors
    }

    resettingFiltersRef.current = false
  }, [form, history, location.pathname])

  useEffect(() => {
    const search = location.search
    if (lastAppliedSearchRef.current === search) return
    lastAppliedSearchRef.current = search

    const params = new URLSearchParams(search)
    if (!hasClusterSummaryUrlParams(params)) return

    const fromUrl = clusterSummaryStateFromSearchParams(params)
    setFilters(fromUrl.filters)
    setSortState(fromUrl.sort)
    setPagination((prev) => ({
      ...prev,
      current: fromUrl.pagination.current,
      pageSize: fromUrl.pagination.pageSize
    }))
    if (fromUrl.filters.taskIds?.length) {
      selectedTaskIdsRef.current = []
      fetchTaskOptions()
    }
  }, [fetchTaskOptions, location.search])

  useEffect(() => {
    try {
      sessionStorage.setItem(
        'k8s_upgrade_cluster_summary_filters',
        JSON.stringify(filters)
      )
    } catch {
      // ignore quota / privacy mode errors
    }
  }, [filters])

  useEffect(() => {
    const nextSearch = buildClusterSummarySearch({
      filters,
      sort: sortState,
      pagination: {
        current:
          pagination.current ?? CLUSTER_SUMMARY_DEFAULT_PAGINATION.current,
        pageSize:
          pagination.pageSize ?? CLUSTER_SUMMARY_DEFAULT_PAGINATION.pageSize
      }
    })
    if (nextSearch === location.search) return

    lastAppliedSearchRef.current = nextSearch
    history.replace({
      pathname: location.pathname,
      search: nextSearch
    })
  }, [
    filters,
    sortState,
    pagination.current,
    pagination.pageSize,
    history,
    location.pathname,
    location.search
  ])

  useEffect(() => {
    if (resettingFiltersRef.current) return
    form.setFieldsValue(filtersToFormValues(filters))
  }, [filters, form])

  const filteredRows = useMemo(() => {
    const tenantLike = normalizeLike(filters.tenant)
    const salesOwnerLike = normalizeLike(filters.salesOwner)
    const tamLike = normalizeLike(filters.tam)
    const k8sLike = normalizeLike(filters.k8sName)
    const upgradeSet = new Set(filters.upgradeStatuses || [])
    const regionSet = new Set(filters.regions || [])
    const projectSet = new Set(filters.projectIds || [])
    const businessRegionSet = new Set(filters.businessRegions || [])
    const clusterLike = normalizeLike(filters.cluster)
    const taskIdSet = new Set(filters.taskIds || [])

    return rows.filter((r) => {
      if (taskIdSet.size > 0 && !taskIdSet.has(r.taskId)) return false
      if (filters.taskStatuses?.length) {
        const status = r.taskStatus === 'reviewing' ? 'approving' : r.taskStatus
        if (!filters.taskStatuses.includes(status as any)) return false
      }
      if (filters.cloudProviders?.length) {
        if (!filters.cloudProviders.includes(r.cloudProvider as any))
          return false
      }
      if (upgradeSet.size > 0) {
        const rowStatus = r.upgradeOperationStatus || ''
        const matched = (filters.upgradeStatuses || []).some((s) => {
          if (s === '') return rowStatus === ''
          return s.toLowerCase() === rowStatus.toLowerCase()
        })
        if (!matched) return false
      }
      if (regionSet.size > 0) {
        if (!regionSet.has(r.region || '')) return false
      }
      if (projectSet.size > 0) {
        if (!projectSet.has(r.projectId || '')) return false
      }
      if (businessRegionSet.size > 0) {
        if (!businessRegionSet.has(r.businessRegion || '')) return false
      }
      if (
        clusterLike &&
        !clusterMatchesQuery(r.k8sSnapshot, filters.cluster || '')
      )
        return false
      if (
        tenantLike &&
        !normalizeLike(r.tenantName).includes(tenantLike) &&
        !normalizeLike(r.tenantId).includes(tenantLike)
      )
        return false
      if (
        salesOwnerLike &&
        !normalizeLike(r.salesOwnerEmail).includes(salesOwnerLike)
      )
        return false
      if (tamLike && !normalizeLike(r.tamName).includes(tamLike)) return false
      if (k8sLike && !normalizeLike(r.k8sName).includes(k8sLike)) return false
      return true
    })
  }, [filters, rows])

  const ensureMaintenancesLoaded = useCallback(
    async (ids: number[]) => {
      const need = ids.filter((id) => {
        const cached = maintenanceCache[id]
        // already have data
        if (cached?.scheduleTime) return false
        // in-flight
        if (cached?.loading) return false
        // terminal failure, never retry
        if (cached?.skipRetry) return false
        return true
      })
      if (need.length === 0) return

      setMaintenanceCache((prev) => {
        const next = { ...prev }
        for (const id of need) next[id] = { ...next[id], loading: true }
        return next
      })

      await Promise.all(
        need.map(async (id) => {
          try {
            const resp = await getMaintenance(id, { with_executions: true })
            const scheduleTime = resp.data.maintenance?.task?.schedule_time
            const status = resp.data.maintenance?.task?.status
            if (!mountedRef.current) return
            setMaintenanceCache((prev) => ({
              ...prev,
              [id]: {
                scheduleTime,
                status,
                loading: false
              }
            }))
          } catch (e: any) {
            if (!mountedRef.current) return
            if (shouldSkipNotFoundLike(e)) {
              setMaintenanceCache((prev) => ({
                ...prev,
                [id]: {
                  error: e?.response?.data?.errmsg || e?.message || 'not found',
                  loading: false,
                  skipRetry: true
                }
              }))
              return
            }
            setMaintenanceCache((prev) => ({
              ...prev,
              [id]: {
                error: e?.message || 'failed',
                loading: false
              }
            }))
          }
        })
      )
    },
    [maintenanceCache]
  )

  const renderTaskStatus = useCallback((status?: string) => {
    const s = status || '-'
    let color: string | undefined
    switch (s) {
      case 'pending':
        color = '#a1a3a6'
        break
      case 'reviewing':
      case 'approving':
        color = 'orange'
        break
      case 'running':
        color = 'blue'
        break
      case 'completed':
        color = 'green'
        break
      case 'cancelled':
        color = '#77787b'
        break
      default:
        color = undefined
    }
    return <Tag color={color}>{s}</Tag>
  }, [])

  const renderUpgradeStatus = useCallback((status?: string) => {
    const s = status || '-'
    let color: string | undefined
    const lower = s.toLowerCase()
    if (lower === 'pending') color = 'orange'
    else if (lower === 'running') color = 'blue'
    else if (lower === 'completed') color = 'green'
    else if (lower === 'failed') color = 'red'
    return <Tag color={color}>{s}</Tag>
  }, [])

  const copyText = useCallback(async (text: string) => {
    try {
      await navigator.clipboard.writeText(text)
    } catch (e) {
      // ignore
    }
  }, [])

  const columns = useMemo((): ColumnsType<ClusterSummaryRow> => {
    const columnSortOrder = (
      columnKey: string,
      dataIndex?: string
    ): SortOrder | null => {
      const key = dataIndex || columnKey
      if (sortState.field === key || sortState.field === columnKey) {
        return sortState.order ?? null
      }
      return null
    }

    return [
      {
        title: 'Task ID',
        dataIndex: 'taskId',
        width: 90,
        fixed: 'left',
        sorter: true,
        sortOrder: columnSortOrder('taskId', 'taskId'),
        render: (_, r) => (
          <Link
            to={`/k8sUpgradeTask/${r.taskId}?name=${encodeURIComponent(
              r.taskName
            )}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            {r.taskId}
          </Link>
        )
      },
      {
        title: 'Task Name',
        dataIndex: 'taskName',
        width: 220,
        sorter: true,
        sortOrder: columnSortOrder('taskName', 'taskName'),
        render: (_, r) => (
          <Typography.Text
            style={{ width: 220 }}
            ellipsis={{ tooltip: r.taskName }}
          >
            {r.taskName}
          </Typography.Text>
        )
      },
      {
        title: 'Task Status',
        dataIndex: 'taskStatus',
        width: 120,
        filters: K8S_UPGRADE_TASK_STATUS_OPTIONS.map((o) => ({
          text: o.label,
          value: o.value
        })),
        filteredValue: filters.taskStatuses?.length
          ? filters.taskStatuses
          : null,
        filterMultiple: true,
        render: (_, r) => renderTaskStatus(r.taskStatus)
      },
      {
        title: 'K8s',
        dataIndex: 'k8sName',
        width: 260,
        sorter: true,
        sortOrder: columnSortOrder('k8sName', 'k8sName'),
        render: (_, r) => {
          const detailUrl = `/k8sUpgradeTask/${
            r.taskId
          }/K8sCluster/${encodeURIComponent(
            r.k8sName
          )}?cloud_provider=${encodeURIComponent(
            r.cloudProvider || ''
          )}&task_status=${encodeURIComponent(
            r.taskStatus
          )}&region=${encodeURIComponent(r.region || '')}`

          return renderNavLabelWithCopy(copyText, {
            label: r.k8sName,
            to: detailUrl,
            copyValue: r.k8sName,
            maxWidth: 190,
            copyTooltip: '复制 K8s 名称',
            openInNewTab: true
          })
        }
      },
      {
        title: 'Tenant',
        dataIndex: 'tenantName',
        width: 220,
        render: (_, r) => {
          const tenant = r.tenantName || r.tenantId || '-'
          const to =
            r.tenantId && tenant !== '-'
              ? `/orgs/${encodeURIComponent(r.tenantId)}`
              : undefined
          return renderNavLabelWithCopy(copyText, {
            label: tenant,
            to,
            copyValue: tenant,
            maxWidth: 150,
            copyTooltip: '复制 Tenant',
            openInNewTab: true
          })
        }
      },
      {
        title: 'Project',
        dataIndex: 'projectId',
        width: 240,
        sorter: true,
        sortOrder: columnSortOrder('projectId', 'projectId'),
        render: (_, r) => {
          const project = r.projectId || '-'
          const to =
            r.tenantId && r.projectId
              ? `/orgs/${encodeURIComponent(
                  r.tenantId
                )}/projects/${encodeURIComponent(r.projectId)}`
              : undefined
          const display = project
          const canCopy = display !== '-'
          const nameNode = to ? (
            <Link
              to={to}
              title={display}
              target="_blank"
              rel="noopener noreferrer"
              style={{
                color: NAV_LINK_COLOR,
                display: 'inline-block',
                whiteSpace: 'nowrap',
                verticalAlign: 'bottom'
              }}
            >
              {display}
            </Link>
          ) : (
            <Typography.Text style={{ whiteSpace: 'nowrap' }}>
              {display}
            </Typography.Text>
          )
          return (
            <Space size={6}>
              {nameNode}
              <Tooltip title="复制 Project">
                <Button
                  size="small"
                  type="text"
                  icon={<CopyOutlined />}
                  disabled={!canCopy}
                  onClick={(e) => {
                    e.stopPropagation()
                    copyText(r.projectId || project)
                  }}
                />
              </Tooltip>
            </Space>
          )
        }
      },
      {
        title: 'Cloud Provider',
        dataIndex: 'cloudProvider',
        width: 140,
        filters: Object.values(ListK8sUpgradeTasksCloudProviderItem).map(
          (v) => ({
            text: v,
            value: v
          })
        ),
        filteredValue: filters.cloudProviders?.length
          ? filters.cloudProviders
          : null,
        filterMultiple: true,
        render: (_, r) => r.cloudProvider || '-'
      },
      {
        title: 'Region',
        dataIndex: 'region',
        width: 140,
        sorter: true,
        sortOrder: columnSortOrder('region', 'region'),
        render: (_, r) => r.region || '-'
      },
      {
        title: 'Schedule Time',
        key: 'maintenanceScheduleTime',
        width: 180,
        sorter: true,
        sortOrder: columnSortOrder('maintenanceScheduleTime'),
        render: (_, r) => {
          const id = r.maintenanceWindowId
          if (!id) return <span style={{ color: '#aaa' }}>-</span>
          const m = maintenanceCache[id]
          if (m?.loading)
            return <span style={{ color: '#aaa' }}>Loading...</span>
          if (m?.error)
            return (
              <Tooltip title={m.error}>
                <span style={{ color: '#cf1322' }}>Failed</span>
              </Tooltip>
            )
          return formatScheduleTimeLocal(m?.scheduleTime)
        }
      },
      {
        title: 'Distance',
        key: 'maintenanceDistance',
        width: 140,
        sorter: true,
        sortOrder: columnSortOrder('maintenanceDistance'),
        render: (_, r) => {
          const id = r.maintenanceWindowId
          const iso = id ? maintenanceCache[id]?.scheduleTime : undefined
          const { text, color } = getDistanceTextAndColor(iso)
          if (!color) return <span style={{ color: '#aaa' }}>{text}</span>
          return <span style={{ color }}>{text}</span>
        }
      },
      {
        title: 'Upgrade Status',
        dataIndex: 'upgradeOperationStatus',
        width: 160,
        filters: UPGRADE_OPERATION_STATUS_OPTIONS.map((o) => ({
          text: o.label,
          value: o.value
        })),
        filteredValue: filters.upgradeStatuses?.length
          ? filters.upgradeStatuses
          : null,
        filterMultiple: true,
        render: (_, r) => renderUpgradeStatus(r.upgradeOperationStatus)
      },
      {
        title: 'Duration',
        dataIndex: 'upgradeDurationMs',
        width: 120,
        sorter: true,
        sortOrder: columnSortOrder('upgradeDurationMs', 'upgradeDurationMs'),
        render: (_, r) => r.upgradeDurationLabel || '-'
      },
      {
        title: 'Maintenance',
        dataIndex: 'maintenanceWindowId',
        width: 130,
        sorter: true,
        render: (_, r) =>
          r.maintenanceWindowId ? (
            <Link
              to={`/maintenance/${r.maintenanceWindowId}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              {r.maintenanceWindowId}
            </Link>
          ) : (
            <span style={{ color: '#aaa' }}>-</span>
          )
      },
      {
        title: 'Business Region',
        dataIndex: 'businessRegion',
        width: 140,
        sorter: true,
        render: (_, r) => r.businessRegion || '-'
      },
      {
        title: 'Control Plane',
        dataIndex: 'controlPlaneVersion',
        width: 120,
        sorter: true,
        render: (_, r) => r.controlPlaneVersion || '-'
      },
      {
        title: 'Desired Version',
        dataIndex: 'desiredVersion',
        width: 140,
        sorter: true,
        sortOrder: columnSortOrder('desiredVersion', 'desiredVersion'),
        render: (_, r) => r.desiredVersion || '-'
      },
      {
        title: 'Total Clusters',
        dataIndex: 'totalClusterCount',
        width: 120,
        sorter: true,
        sortOrder: columnSortOrder('totalClusterCount', 'totalClusterCount'),
        render: (_, r) =>
          r.totalClusterCount != null ? r.totalClusterCount : '-'
      },
      {
        title: 'Upgrade Clusters',
        dataIndex: 'upgradeClusterCount',
        width: 130,
        sorter: true,
        sortOrder: columnSortOrder(
          'upgradeClusterCount',
          'upgradeClusterCount'
        ),
        render: (_, r) =>
          r.upgradeClusterCount != null ? r.upgradeClusterCount : '-'
      },
      {
        title: 'Sales Owner',
        dataIndex: 'salesOwnerEmail',
        width: 200,
        render: (_, r) => {
          const email = r.salesOwnerEmail || '-'
          return renderNavLabelWithCopy(copyText, {
            label: email,
            copyValue: email,
            maxWidth: 170,
            copyTooltip: '复制 Sales Owner Email'
          })
        }
      },
      {
        title: 'TAM',
        dataIndex: 'tamName',
        width: 160,
        render: (_, r) => {
          const name = r.tamName || '-'
          return renderNavLabelWithCopy(copyText, {
            label: name,
            copyValue: name,
            maxWidth: 130,
            copyTooltip: '复制 TAM'
          })
        }
      }
    ]
  }, [
    copyText,
    filters.cloudProviders,
    filters.taskStatuses,
    filters.upgradeStatuses,
    maintenanceCache,
    renderTaskStatus,
    renderUpgradeStatus,
    sortState
  ])

  const handleTableChange = useCallback(
    (
      p: TablePaginationConfig,
      tableFilters: Record<string, any>,
      sorter: any
    ) => {
      setPagination((prev) => ({
        ...prev,
        ...(p.current != null ? { current: p.current } : {}),
        ...(p.pageSize != null ? { pageSize: p.pageSize } : {})
      }))

      const patch: Partial<FiltersState> = {}
      if (Object.prototype.hasOwnProperty.call(tableFilters, 'taskStatus')) {
        const v = tableFilters.taskStatus
        patch.taskStatuses = Array.isArray(v) && v.length ? v : undefined
      }
      if (Object.prototype.hasOwnProperty.call(tableFilters, 'cloudProvider')) {
        const v = tableFilters.cloudProvider
        patch.cloudProviders = Array.isArray(v) && v.length ? v : undefined
      }
      if (
        Object.prototype.hasOwnProperty.call(
          tableFilters,
          'upgradeOperationStatus'
        )
      ) {
        const v = tableFilters.upgradeOperationStatus
        patch.upgradeStatuses = Array.isArray(v) && v.length ? v : undefined
      }

      const eq = (a: unknown, b: unknown) =>
        JSON.stringify(a ?? null) === JSON.stringify(b ?? null)

      const tableFilterDirty =
        (Object.prototype.hasOwnProperty.call(patch, 'taskStatuses') &&
          !eq(patch.taskStatuses, filters.taskStatuses)) ||
        (Object.prototype.hasOwnProperty.call(patch, 'cloudProviders') &&
          !eq(patch.cloudProviders, filters.cloudProviders)) ||
        (Object.prototype.hasOwnProperty.call(patch, 'upgradeStatuses') &&
          !eq(patch.upgradeStatuses, filters.upgradeStatuses))

      if (tableFilterDirty) {
        setPagination((prev) => ({ ...prev, current: 1 }))
      }
      if (Object.keys(patch).length > 0) {
        applyFilters(patch)
      }

      if (!Array.isArray(sorter)) {
        const order = sorter.order as SortOrder
        if (!order) {
          setSortState({ ...CLUSTER_SUMMARY_DEFAULT_SORT })
          return
        }
        const field = sorter.field || sorter.columnKey
        setSortState({
          field:
            field === 'maintenanceScheduleTime'
              ? 'maintenanceScheduleTime'
              : field,
          order
        })
      }
    },
    [
      applyFilters,
      filters.cloudProviders,
      filters.taskStatuses,
      filters.upgradeStatuses
    ]
  )

  // inject computed sort fields for sorting logic
  const tableData = useMemo(() => {
    return rows.map((r) => {
      const iso =
        r.maintenanceWindowId != null
          ? maintenanceCache[r.maintenanceWindowId]?.scheduleTime
          : undefined
      const scheduleMs = iso ? moment(iso).valueOf() : undefined
      const distanceMs = iso ? moment(iso).valueOf() - Date.now() : undefined
      return {
        ...r,
        maintenanceScheduleTime: scheduleMs ?? Number.POSITIVE_INFINITY,
        maintenanceDistance: distanceMs ?? Number.POSITIVE_INFINITY
      } as any
    })
  }, [maintenanceCache, rows])

  const effectiveFilteredRows = useMemo(() => {
    // re-run filters on enriched tableData so sorting keys exist
    const index = new Map<string, any>()
    for (const r of tableData) index.set(r.key, r)
    return filteredRows.map((r) => index.get(r.key)).filter(Boolean)
  }, [filteredRows, tableData])

  const effectiveSortedRows = useMemo(() => {
    const { field, order } = sortState
    if (isDefaultClusterSummarySort(field, order)) {
      return [...effectiveFilteredRows].sort(compareDefaultClusterSummaryRows)
    }

    if (!field || !order) {
      return [...effectiveFilteredRows].sort(compareDefaultClusterSummaryRows)
    }

    const sortField = field
    const dir = order === 'ascend' ? 1 : -1
    const copy = [...effectiveFilteredRows]
    copy.sort((a, b) => {
      const av = a[sortField]
      const bv = b[sortField]
      if (av == null && bv == null) return 0
      if (av == null) return 1
      if (bv == null) return -1
      if (typeof av === 'number' && typeof bv === 'number')
        return dir * (av - bv)
      return dir * String(av).localeCompare(String(bv))
    })
    return copy
  }, [effectiveFilteredRows, sortState])

  const summaryStats = useMemo(() => {
    const taskIds = new Set<number>()
    const upgradeStatusCounts: Record<string, number> = {}
    for (const r of effectiveSortedRows) {
      taskIds.add(r.taskId)
      const status = r.upgradeOperationStatus || '-'
      upgradeStatusCounts[status] = (upgradeStatusCounts[status] || 0) + 1
    }
    const sortedStatuses = Object.keys(upgradeStatusCounts).sort((a, b) =>
      a.localeCompare(b)
    )
    return {
      taskCount: taskIds.size,
      recordCount: effectiveSortedRows.length,
      upgradeStatusCounts,
      sortedStatuses
    }
  }, [effectiveSortedRows])

  const heatmapLoading = useMemo(() => {
    for (const r of effectiveFilteredRows) {
      const id = r.maintenanceWindowId
      if (id == null) continue
      const cached = maintenanceCache[id]
      if (cached?.scheduleTime || cached?.skipRetry || cached?.error) continue
      return true
    }
    return false
  }, [effectiveFilteredRows, maintenanceCache])

  const effectivePageRows = useMemo(() => {
    const current = pagination.current || 1
    const pageSize = pagination.pageSize || 20
    const start = (current - 1) * pageSize
    return effectiveSortedRows.slice(start, start + pageSize)
  }, [effectiveSortedRows, pagination.current, pagination.pageSize])

  useEffect(() => {
    setPagination((p) => {
      const pageSize = p.pageSize || 20
      const total = effectiveSortedRows.length
      const maxPage = Math.max(1, Math.ceil(total / pageSize) || 1)
      const cur = p.current || 1
      if (cur > maxPage) return { ...p, current: maxPage }
      return p
    })
  }, [effectiveSortedRows.length, pagination.pageSize])

  useEffect(() => {
    const ids = uniq(
      effectiveFilteredRows
        .map((r) => r.maintenanceWindowId)
        .filter((v): v is number => typeof v === 'number')
    )
    if (ids.length) ensureMaintenancesLoaded(ids)
  }, [effectiveFilteredRows, ensureMaintenancesLoaded])

  const exportCsv = useCallback(() => {
    const lines = [CLUSTER_SUMMARY_CSV_HEADERS.map(toCsvValue).join(',')]
    for (const r of effectiveSortedRows) {
      lines.push(buildClusterSummaryCsvRow(r).map(toCsvValue).join(','))
    }

    const content = `\uFEFF${lines.join('\r\n')}`
    const blob = new Blob([content], { type: 'text/csv;charset=utf-8' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `k8s-upgrade-clusters-${moment()
      .format('YYYY-MM-DD_HH-mm-ss')
      .toString()}.csv`
    a.click()
    URL.revokeObjectURL(url)
  }, [effectiveSortedRows])

  const toolbarActions = useMemo(() => {
    const items: React.ReactNode[] = [
      <div
        key="refresh"
        style={{ position: 'relative', display: 'inline-block' }}
      >
        <Space size={0}>
          <Button
            icon={<ReloadOutlined />}
            type="primary"
            onClick={() => {
              refreshSelectedTaskDetails(filters.taskIds || [])
              resetAutoRefreshCountdown()
            }}
            loading={loading}
            disabled={!filters.taskIds?.length}
          >
            Refresh
          </Button>
          <Dropdown overlay={autoRefreshMenu} trigger={['click']}>
            <Button
              type="primary"
              disabled={!filters.taskIds?.length}
              style={{ borderLeft: 0 }}
            >
              {autoRefreshLabel}
            </Button>
          </Dropdown>
        </Space>
        {autoRefreshEnabled && autoRefreshIntervalSec ? (
          <div
            style={{
              position: 'absolute',
              left: 0,
              right: 0,
              bottom: 0,
              height: 2,
              background: 'rgba(0,0,0,0.15)',
              overflow: 'hidden',
              pointerEvents: 'none',
              borderBottomLeftRadius: 2,
              borderBottomRightRadius: 2
            }}
          >
            <div
              style={{
                position: 'absolute',
                left: 0,
                top: 0,
                bottom: 0,
                width: `${Math.max(
                  0,
                  Math.min(100, 100 - autoRefreshPercent)
                )}%`,
                height: '100%',
                background: '#faad14'
              }}
            />
          </div>
        ) : null}
      </div>,
      <Button
        key="export"
        icon={<DownloadOutlined />}
        onClick={exportCsv}
        disabled={effectiveSortedRows.length === 0}
      >
        Export CSV
      </Button>,
      <UpgradeScheduleHeatmapTrigger
        key="heatmap"
        rows={effectiveFilteredRows}
        maintenanceCache={maintenanceCache}
        loading={heatmapLoading}
        disabled={effectiveFilteredRows.length === 0}
      />
    ]

    if (canSendUpgradeNotification) {
      if (selectedTaskIds.length === 1) {
        items.push(
          <Button
            key="notice"
            icon={<BellOutlined />}
            onClick={() => openNotifyModal(selectedTaskIds[0])}
            disabled={!selectedTaskIds.length}
          >
            Upgrade Notification
          </Button>
        )
      } else if (selectedTaskIds.length > 1) {
        items.push(
          <Dropdown key="notice" overlay={noticeMenu!} trigger={['click']}>
            <Button icon={<BellOutlined />} disabled={!selectedTaskIds.length}>
              Upgrade Notification
            </Button>
          </Dropdown>
        )
      } else {
        items.push(
          <Button key="notice" icon={<BellOutlined />} disabled>
            Upgrade Notification
          </Button>
        )
      }
    }

    return items
  }, [
    autoRefreshEnabled,
    autoRefreshIntervalSec,
    autoRefreshLabel,
    autoRefreshMenu,
    autoRefreshPercent,
    effectiveSortedRows.length,
    exportCsv,
    effectiveFilteredRows,
    heatmapLoading,
    maintenanceCache,
    filters.taskIds,
    canSendUpgradeNotification,
    loading,
    noticeMenu,
    openNotifyModal,
    refreshSelectedTaskDetails,
    resetAutoRefreshCountdown,
    selectedTaskIds
  ])

  return (
    <>
      <ListToolBar title="ClusterSummary" actions={toolbarActions} />

      <Card bordered={false}>
        {progressText ? (
          <>
            <Alert type="info" showIcon message={progressText} />
            <Divider />
          </>
        ) : null}
        {loadError ? (
          <>
            <Alert
              type="error"
              showIcon
              message="数据加载失败"
              description={loadError}
            />
            <Divider />
          </>
        ) : null}

        <Collapse defaultActiveKey={['basic']}>
          <Panel header="Filters" key="basic">
            <Form
              form={form}
              layout="vertical"
              size="middle"
              initialValues={filters}
              onValuesChange={(_, values) => {
                if (resettingFiltersRef.current) return
                setPagination((p) => ({ ...p, current: 1 }))
                setFilters(values as FiltersState)
              }}
            >
              <div
                style={{
                  background: '#fafafa',
                  padding: 8,
                  borderRadius: 6,
                  border: '1px solid #f0f0f0'
                }}
              >
                <Row gutter={[10, 4]}>
                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Task"
                      name="taskIds"
                      style={{ marginBottom: 8 }}
                    >
                      <Select<number[]>
                        mode="multiple"
                        allowClear
                        showSearch
                        placeholder="选择任务"
                        style={{ width: '100%' }}
                        options={allTaskIdOptions}
                        optionFilterProp="label"
                        maxTagCount={1}
                        loading={taskOptionsLoading}
                        onDropdownVisibleChange={(open) => {
                          if (open && tasks.length === 0) fetchTaskOptions()
                        }}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Task Status"
                      name="taskStatuses"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        placeholder="task status"
                        style={{ width: '100%' }}
                        options={K8S_UPGRADE_TASK_STATUS_OPTIONS}
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Tenant"
                      name="tenant"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        allowClear
                        showSearch
                        placeholder="tenant name/id"
                        style={{ width: '100%' }}
                        options={allTenantOptions}
                        optionFilterProp="label"
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Sales Owner"
                      name="salesOwner"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        allowClear
                        showSearch
                        placeholder="sales owner email"
                        style={{ width: '100%' }}
                        options={allSalesOwnerOptions}
                        optionFilterProp="label"
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="TAM"
                      name="tam"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        allowClear
                        showSearch
                        placeholder="tam name"
                        style={{ width: '100%' }}
                        options={allTamOptions}
                        optionFilterProp="label"
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Cloud Provider"
                      name="cloudProviders"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        placeholder="cloud provider"
                        style={{ width: '100%' }}
                        options={Object.values(
                          ListK8sUpgradeTasksCloudProviderItem
                        ).map((v) => ({ label: v, value: v }))}
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="K8s"
                      name="k8sName"
                      style={{ marginBottom: 8 }}
                    >
                      <Input
                        allowClear
                        placeholder="k8s name"
                        style={{ width: '100%' }}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Project"
                      name="projectIds"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        showSearch
                        placeholder="project id"
                        style={{ width: '100%' }}
                        options={allProjectOptions}
                        optionFilterProp="label"
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Cluster"
                      name="cluster"
                      style={{ marginBottom: 8 }}
                    >
                      <Input
                        allowClear
                        placeholder="TiDB cluster id/name"
                        style={{ width: '100%' }}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Business Region"
                      name="businessRegions"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        placeholder="business region"
                        style={{ width: '100%' }}
                        options={allBusinessRegionOptions.map((v) => ({
                          label: v,
                          value: v
                        }))}
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Region"
                      name="regions"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        placeholder="region"
                        style={{ width: '100%' }}
                        options={allRegionOptions.map((v) => ({
                          label: v,
                          value: v
                        }))}
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24} sm={12} md={8} lg={6} xl={6}>
                    <Form.Item
                      label="Upgrade Status"
                      name="upgradeStatuses"
                      style={{ marginBottom: 8 }}
                    >
                      <Select
                        mode="multiple"
                        allowClear
                        placeholder="upgrade status"
                        style={{ width: '100%' }}
                        options={UPGRADE_OPERATION_STATUS_OPTIONS}
                        maxTagCount={1}
                      />
                    </Form.Item>
                  </Col>

                  <Col xs={24}>
                    <Space>
                      <Button onClick={resetAllFilters}>Reset</Button>
                    </Space>
                  </Col>
                </Row>
              </div>
            </Form>
          </Panel>
        </Collapse>

        <Divider />

        <div
          style={{
            marginBottom: 12,
            padding: '10px 12px',
            background: '#fafafa',
            borderRadius: 6,
            border: '1px solid #f0f0f0'
          }}
        >
          <Space size={[16, 8]} wrap>
            <Typography.Text>
              Total Tasks:{' '}
              <Typography.Text strong>{summaryStats.taskCount}</Typography.Text>
            </Typography.Text>
            <Typography.Text>
              Total Records:{' '}
              <Typography.Text strong>
                {summaryStats.recordCount}
              </Typography.Text>
            </Typography.Text>
            {summaryStats.sortedStatuses.length > 0 ? (
              <>
                <Divider type="vertical" style={{ margin: 0 }} />
                <Typography.Text type="secondary">
                  Upgrade Status：
                </Typography.Text>
                {summaryStats.sortedStatuses.map((status) => (
                  <Space key={status} size={4}>
                    {renderUpgradeStatus(status === '-' ? undefined : status)}
                    <Typography.Text>
                      {summaryStats.upgradeStatusCounts[status]}
                    </Typography.Text>
                  </Space>
                ))}
              </>
            ) : null}
          </Space>
        </div>

        <Table<any>
          columns={columns}
          dataSource={effectivePageRows}
          rowKey="key"
          loading={loading}
          size="small"
          scroll={{ x: 3160 }}
          expandable={{
            expandedRowRender: (record) => <ExpandedRowDetail row={record} />,
            expandIcon: ({ expanded, onExpand, record }) => (
              <span
                role="button"
                aria-expanded={expanded}
                aria-label={expanded ? 'Collapse row' : 'Expand row'}
                style={{
                  display: 'inline-flex',
                  alignItems: 'center',
                  cursor: 'pointer',
                  color: 'rgba(0, 0, 0, 0.45)',
                  fontSize: 12,
                  marginRight: 8,
                  transition: 'transform 0.24s'
                }}
                onClick={(e) => onExpand(record, e)}
              >
                <RightOutlined rotate={expanded ? 90 : 0} />
              </span>
            )
          }}
          pagination={{
            ...pagination,
            total: effectiveSortedRows.length,
            showTotal: (total) => `Total ${total}`
          }}
          onChange={handleTableChange}
        />
      </Card>

      {canSendUpgradeNotification ? (
        <NotifyUpgradeModal
          taskId={notifyTaskId}
          loading={notifyLoading}
          onLoadingChange={setNotifyLoading}
          onSuccess={() => message.success('Upgrade notification sent')}
          onCancel={() => setNotifyTaskId(null)}
        />
      ) : null}
    </>
  )
}
