import { useEffect } from 'react'

type SortDirection = 'ascending' | 'descending'

type TableSortState = {
  columnIndex: number
  direction: SortDirection
}

const ACTION_HEADER_PATTERN = /^(action|actions|option|options|edit|delete|remove|preview|print|open|link)$/i
const CUSTOM_SORT_SELECTOR = [
  'thead th[role="button"]',
  'thead th button',
  'thead th a',
  'thead th input',
  'thead th select',
  'thead th textarea',
].join(', ')

function headerText(cell: HTMLTableCellElement): string {
  return (cell.textContent ?? '').replace(/\s+/g, ' ').trim()
}

function parseBooleanValue(value: string): number | null {
  const normalized = value.trim().toLowerCase()
  if (!normalized) return null
  if (['yes', 'true', 'active', 'enabled', 'paid', 'present'].includes(normalized)) return 1
  if (['no', 'false', 'inactive', 'disabled', 'unpaid', 'absent'].includes(normalized)) return 0
  return null
}

function parseNumberValue(value: string): number | null {
  const normalized = value.trim()
  if (!normalized) return null
  if (!/^\(?\s*[$€£]?\s*-?[\d,.]+%?\s*\)?$/.test(normalized)) return null

  const negative = normalized.includes('(') && normalized.includes(')')
  const cleaned = normalized.replace(/[%,$€£()\s]/g, '').replace(/,/g, '')
  if (!cleaned || cleaned === '-' || cleaned === '.' || cleaned === '-.') return null

  const parsed = Number(cleaned)
  if (!Number.isFinite(parsed)) return null
  return negative ? -parsed : parsed
}

function parseDateValue(value: string): number | null {
  const normalized = value.trim()
  if (!normalized) return null
  const parsed = Date.parse(normalized)
  return Number.isNaN(parsed) ? null : parsed
}

function cellSortValue(cell: HTMLTableCellElement | null): string {
  if (!cell) return ''
  const dataValue = cell.getAttribute('data-sort-value')
  if (dataValue != null) return dataValue.trim()
  return (cell.textContent ?? '').replace(/\s+/g, ' ').trim()
}

function getLogicalCellForColumn(row: HTMLTableRowElement, columnIndex: number): HTMLTableCellElement | null {
  let cursor = 0
  for (const cell of Array.from(row.cells)) {
    const span = Math.max(1, cell.colSpan || 1)
    if (columnIndex >= cursor && columnIndex < cursor + span) {
      return cell
    }
    cursor += span
  }
  return null
}

function compareValues(left: string, right: string): number {
  const leftBoolean = parseBooleanValue(left)
  const rightBoolean = parseBooleanValue(right)
  if (leftBoolean != null && rightBoolean != null) return leftBoolean - rightBoolean

  const leftNumber = parseNumberValue(left)
  const rightNumber = parseNumberValue(right)
  if (leftNumber != null && rightNumber != null) return leftNumber - rightNumber

  const leftDate = parseDateValue(left)
  const rightDate = parseDateValue(right)
  if (leftDate != null && rightDate != null) return leftDate - rightDate

  return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' })
}

function tableSupportsAutoSort(table: HTMLTableElement): boolean {
  if (table.classList.contains('ci-no-auto-sort')) return false
  if (table.querySelector(CUSTOM_SORT_SELECTOR)) return false
  if (table.querySelector('tbody td[rowspan], tbody th[rowspan]')) return false

  const head = table.tHead
  const headerRow = head?.rows.item(head.rows.length - 1)
  if (!headerRow || headerRow.cells.length === 0) return false

  const bodyRows = Array.from(table.tBodies).flatMap((body) => Array.from(body.rows))
  if (bodyRows.length < 2) return false

  return !Array.from(headerRow.cells).some((cell) => window.getComputedStyle(cell).cursor === 'pointer')
}

function updateHeaderState(
  headers: Array<{ cell: HTMLTableCellElement; columnIndex: number }>,
  sortState: TableSortState | undefined,
) {
  for (const { cell, columnIndex } of headers) {
    const active = sortState?.columnIndex === columnIndex
    const direction = active ? sortState?.direction : null
    cell.dataset.ciSortDirection =
      direction === 'ascending' ? 'ascending' : direction === 'descending' ? 'descending' : 'none'
    cell.setAttribute('aria-sort', direction ?? 'none')
  }
}

function sortTableRows(table: HTMLTableElement, columnIndex: number, direction: SortDirection) {
  const factor = direction === 'ascending' ? 1 : -1
  for (const body of Array.from(table.tBodies)) {
    const rows = Array.from(body.rows)
    if (rows.length < 2) continue

    const sortedRows = [...rows].sort((left, right) => {
      const leftValue = cellSortValue(getLogicalCellForColumn(left, columnIndex))
      const rightValue = cellSortValue(getLogicalCellForColumn(right, columnIndex))
      return compareValues(leftValue, rightValue) * factor
    })

    for (const row of sortedRows) {
      body.appendChild(row)
    }
  }
}

export function GlobalTableSorting() {
  useEffect(() => {
    if (typeof document === 'undefined') return

    const enhancedHeaders = new WeakSet<HTMLTableCellElement>()
    const tableSortStates = new WeakMap<HTMLTableElement, TableSortState>()
    const cleanupCallbacks = new WeakMap<HTMLTableCellElement, () => void>()
    let applyingSort = false
    let frameId = 0

    function bindTables() {
      frameId = 0

      for (const table of Array.from(document.querySelectorAll('table'))) {
        if (!(table instanceof HTMLTableElement)) continue
        if (!tableSupportsAutoSort(table)) continue

        const head = table.tHead
        const headerRow = head?.rows.item(head.rows.length - 1)
        if (!headerRow) continue

        const headers: Array<{ cell: HTMLTableCellElement; columnIndex: number }> = []
        let columnCursor = 0

        for (const headerCell of Array.from(headerRow.cells)) {
          const span = Math.max(1, headerCell.colSpan || 1)
          const label = headerText(headerCell)
          const columnIndex = columnCursor
          const isActionColumn = ACTION_HEADER_PATTERN.test(label)
          columnCursor += span

          if (span !== 1 || isActionColumn) {
            headerCell.classList.remove('ci-sortable-column')
            headerCell.removeAttribute('tabindex')
            headerCell.removeAttribute('aria-sort')
            delete headerCell.dataset.ciSortDirection
            continue
          }

          headers.push({ cell: headerCell, columnIndex })

          if (enhancedHeaders.has(headerCell)) continue

          enhancedHeaders.add(headerCell)
          headerCell.classList.add('ci-sortable-column')
          headerCell.tabIndex = 0
          headerCell.setAttribute('aria-label', label ? `Sort by ${label}` : 'Sort table')

          const activateSort = () => {
            const current = tableSortStates.get(table)
            const nextDirection: SortDirection =
              current?.columnIndex === columnIndex && current.direction === 'ascending'
                ? 'descending'
                : 'ascending'

            const nextState = { columnIndex, direction: nextDirection }
            tableSortStates.set(table, nextState)
            updateHeaderState(headers, nextState)

            applyingSort = true
            sortTableRows(table, columnIndex, nextDirection)
            queueMicrotask(() => {
              applyingSort = false
            })
          }

          const onKeyDown = (event: KeyboardEvent) => {
            if (event.key !== 'Enter' && event.key !== ' ') return
            event.preventDefault()
            activateSort()
          }

          headerCell.addEventListener('click', activateSort)
          headerCell.addEventListener('keydown', onKeyDown)

          cleanupCallbacks.set(headerCell, () => {
            headerCell.removeEventListener('click', activateSort)
            headerCell.removeEventListener('keydown', onKeyDown)
          })
        }

        const sortState = tableSortStates.get(table)
        updateHeaderState(headers, sortState)

        if (sortState) {
          applyingSort = true
          sortTableRows(table, sortState.columnIndex, sortState.direction)
          queueMicrotask(() => {
            applyingSort = false
          })
        }
      }
    }

    const scheduleBind = () => {
      if (frameId !== 0) return
      frameId = window.requestAnimationFrame(bindTables)
    }

    bindTables()

    const observer = new MutationObserver(() => {
      if (applyingSort) return
      scheduleBind()
    })

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    })

    return () => {
      observer.disconnect()
      if (frameId !== 0) {
        window.cancelAnimationFrame(frameId)
      }

      for (const table of Array.from(document.querySelectorAll('table'))) {
        if (!(table instanceof HTMLTableElement)) continue
        const head = table.tHead
        const headerRow = head?.rows.item(head.rows.length - 1)
        if (!headerRow) continue

        for (const headerCell of Array.from(headerRow.cells)) {
          cleanupCallbacks.get(headerCell)?.()
        }
      }
    }
  }, [])

  return null
}
