'use client'

import { useMemo, useState, type ReactNode } from 'react'

import { useI18n } from '@/components/providers/i18n-provider'
import { Input, Select } from '@/components/ui/controls'
import { EmptyState } from '@/components/ui/display'
import { IconSearch } from '@/components/ui/icons'
import { cn } from '@/lib/cn'

export interface Column<T> {
  key: string
  header: string
  /** What to render in the cell. */
  cell: (row: T) => ReactNode
  /** The value to sort by. Omit to make the column unsortable. */
  sortValue?: (row: T) => string | number
  align?: 'start' | 'end' | 'center'
  className?: string
  /** Hidden below the `sm` breakpoint — used to keep mobile tables readable (§46). */
  secondary?: boolean
}

export interface TableFilter {
  key: string
  label: string
  options: Array<{ value: string; label: string }>
}

/**
 * The table used across the console.
 *
 * Sorting and pagination happen client-side, which is right for the page sizes here
 * (the largest list is a few hundred rows and arrives already scoped by the server).
 * Anything genuinely large — sensor readings, audit history — is paginated by the API
 * instead and never reaches this component whole.
 */
export function DataTable<T>({
  rows,
  columns,
  getRowKey,
  searchable,
  searchPlaceholder,
  filters,
  pageSize = 25,
  emptyTitle,
  emptyBody,
  onRowClick,
  initialSort,
  caption,
  dense = false,
  rowClassName,
}: {
  rows: T[]
  columns: Array<Column<T>>
  getRowKey: (row: T) => string
  searchable?: (row: T) => string
  searchPlaceholder?: string
  filters?: Array<TableFilter & { match: (row: T, value: string) => boolean }>
  pageSize?: number
  emptyTitle?: string
  emptyBody?: string
  onRowClick?: (row: T) => void
  initialSort?: { key: string; direction: 'asc' | 'desc' }
  caption: string
  dense?: boolean
  rowClassName?: (row: T) => string | undefined
}) {
  const { t, n } = useI18n()
  const [query, setQuery] = useState('')
  const [sort, setSort] = useState(initialSort ?? null)
  const [page, setPage] = useState(1)
  const [filterValues, setFilterValues] = useState<Record<string, string>>({})

  const filtered = useMemo(() => {
    let result = rows

    if (query && searchable) {
      const needle = query.trim().toLowerCase()
      result = result.filter((row) => searchable(row).toLowerCase().includes(needle))
    }

    for (const filter of filters ?? []) {
      const value = filterValues[filter.key]
      if (value && value !== 'all') {
        result = result.filter((row) => filter.match(row, value))
      }
    }

    if (sort) {
      const column = columns.find((entry) => entry.key === sort.key)
      if (column?.sortValue) {
        const direction = sort.direction === 'asc' ? 1 : -1
        result = [...result].sort((a, b) => {
          const av = column.sortValue!(a)
          const bv = column.sortValue!(b)
          if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * direction
          return String(av).localeCompare(String(bv)) * direction
        })
      }
    }

    return result
  }, [rows, query, searchable, filters, filterValues, sort, columns])

  const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize))
  const safePage = Math.min(page, pageCount)
  const visible = filtered.slice((safePage - 1) * pageSize, safePage * pageSize)

  const toggleSort = (key: string) => {
    setPage(1)
    setSort((current) =>
      current?.key === key
        ? { key, direction: current.direction === 'asc' ? 'desc' : 'asc' }
        : { key, direction: 'desc' },
    )
  }

  const hasControls = Boolean(searchable) || (filters?.length ?? 0) > 0

  return (
    <div>
      {hasControls ? (
        <div className="mb-3 flex flex-wrap items-center gap-2">
          {searchable ? (
            <div className="relative min-w-48 flex-1 sm:max-w-xs">
              <IconSearch className="pointer-events-none absolute top-1/2 size-4 -translate-y-1/2 text-text-faint" style={{ insetInlineStart: '0.65rem' }} />
              <Input
                value={query}
                onChange={(event) => {
                  setQuery(event.target.value)
                  setPage(1)
                }}
                placeholder={searchPlaceholder ?? t('common.searchPlaceholder')}
                aria-label={t('common.search')}
                className="ps-9"
              />
            </div>
          ) : null}

          {filters?.map((filter) => (
            <Select
              key={filter.key}
              aria-label={filter.label}
              value={filterValues[filter.key] ?? 'all'}
              onChange={(event) => {
                setFilterValues((current) => ({ ...current, [filter.key]: event.target.value }))
                setPage(1)
              }}
              className="w-auto min-w-36"
            >
              <option value="all">{filter.label}</option>
              {filter.options.map((option) => (
                <option key={option.value} value={option.value}>
                  {option.label}
                </option>
              ))}
            </Select>
          ))}

          <span className="ms-auto text-xs text-text-faint">
            {t('common.showingCount', { shown: visible.length, total: filtered.length })}
          </span>
        </div>
      ) : null}

      {filtered.length === 0 ? (
        <EmptyState
          title={emptyTitle ?? t('common.noResults')}
          body={emptyBody ?? t('common.noResultsHint')}
        />
      ) : (
        <div className="-mx-4 overflow-x-auto sm:mx-0">
          <table className="w-full min-w-full border-collapse text-sm">
            <caption className="sr-only">{caption}</caption>
            <thead>
              <tr className="border-b border-border">
                {columns.map((column) => {
                  const sorted = sort?.key === column.key
                  return (
                    <th
                      key={column.key}
                      scope="col"
                      aria-sort={
                        sorted ? (sort!.direction === 'asc' ? 'ascending' : 'descending') : undefined
                      }
                      className={cn(
                        'px-3 py-2.5 text-[11px] font-semibold uppercase tracking-wide text-text-faint',
                        column.align === 'end'
                          ? 'text-end'
                          : column.align === 'center'
                            ? 'text-center'
                            : 'text-start',
                        column.secondary && 'hidden md:table-cell',
                      )}
                    >
                      {column.sortValue ? (
                        <button
                          type="button"
                          onClick={() => toggleSort(column.key)}
                          aria-label={t('a11y.sortBy', { column: column.header })}
                          className={cn(
                            'inline-flex items-center gap-1 transition-colors hover:text-text',
                            sorted && 'text-brand',
                          )}
                        >
                          {column.header}
                          <span aria-hidden className="text-[9px]">
                            {sorted ? (sort!.direction === 'asc' ? '▲' : '▼') : '⇅'}
                          </span>
                        </button>
                      ) : (
                        column.header
                      )}
                    </th>
                  )
                })}
              </tr>
            </thead>
            <tbody>
              {visible.map((row) => (
                <tr
                  key={getRowKey(row)}
                  onClick={onRowClick ? () => onRowClick(row) : undefined}
                  className={cn(
                    'border-b border-border/60 transition-colors',
                    onRowClick && 'cursor-pointer hover:bg-surface-2/60',
                    rowClassName?.(row),
                  )}
                >
                  {columns.map((column) => (
                    <td
                      key={column.key}
                      className={cn(
                        dense ? 'px-3 py-1.5' : 'px-3 py-2.5',
                        column.align === 'end'
                          ? 'text-end'
                          : column.align === 'center'
                            ? 'text-center'
                            : 'text-start',
                        column.secondary && 'hidden md:table-cell',
                        column.className,
                      )}
                    >
                      {column.cell(row)}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {pageCount > 1 ? (
        <nav
          aria-label={t('common.page')}
          className="mt-3 flex items-center justify-between gap-3 border-t border-border pt-3"
        >
          <button
            type="button"
            disabled={safePage <= 1}
            onClick={() => setPage((current) => Math.max(1, current - 1))}
            className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-muted transition-colors hover:text-text disabled:opacity-40"
          >
            {t('common.previous')}
          </button>
          <span className="tnum text-xs text-text-faint">
            {t('common.pageOf', { page: n(safePage), pages: n(pageCount) })}
          </span>
          <button
            type="button"
            disabled={safePage >= pageCount}
            onClick={() => setPage((current) => Math.min(pageCount, current + 1))}
            className="rounded-lg border border-border px-3 py-1.5 text-xs text-text-muted transition-colors hover:text-text disabled:opacity-40"
          >
            {t('common.next')}
          </button>
        </nav>
      ) : null}
    </div>
  )
}
