import { useEffect, useRef, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, ListFilter, X } from "lucide-react";
import { useDismiss } from "../lib/useDismiss";

// A reusable, client-side data table: per-column sort + filter (in the header) + pagination,
// with its sort/filter/page state optionally persisted to localStorage so a reload (F5) keeps
// the view. Columns are declared by the caller; the table is generic over the row type, so
// other modules (e.g. the playlist manager) can reuse it. Below `md` it falls back to a card
// list built from the same column definitions.

type ColumnFilter<T> =
  | { kind: "text"; get: (row: T) => string }
  | { kind: "select"; options: { value: string; label: string }[]; test: (row: T, value: string) => boolean }
  | { kind: "multi"; options: { value: string; label: string }[]; test: (row: T, values: string[]) => boolean };

export interface Column<T> {
  key: string;
  header: string;
  render: (row: T) => ReactNode;
  align?: "left" | "right" | "center";
  width?: string;
  sortable?: boolean;
  sortValue?: (row: T) => string | number;
  filter?: ColumnFilter<T>;
  // Keep the cell on one line so the column auto-sizes to its content (table-layout: auto).
  nowrap?: boolean;
  // Card fallback (below md): `cardPrimary` renders as the card heading (no label); `hideInCard`
  // omits the column; `cardLabel:false` shows the value without its header label.
  cardPrimary?: boolean;
  hideInCard?: boolean;
  cardLabel?: boolean;
}

type SortState = { key: string; dir: "asc" | "desc" } | null;
type FilterMap = Record<string, string | string[]>;
interface Persisted {
  sort: SortState;
  filters: FilterMap;
  page: number;
  size?: number;
}

function loadPersist(key?: string): Persisted {
  const empty: Persisted = { sort: null, filters: {}, page: 0 };
  if (!key) return empty;
  try {
    const v = JSON.parse(localStorage.getItem(key) || "{}");
    return { sort: v.sort ?? null, filters: v.filters ?? {}, page: v.page ?? 0, size: v.size };
  } catch {
    return empty;
  }
}

function isActive(value: string | string[] | undefined): boolean {
  return Array.isArray(value) ? value.length > 0 : !!value;
}

export default function DataTable<T>({
  rows,
  columns,
  rowKey,
  pageSize = 10,
  pageSizeOptions = [10, 25, 50, 100],
  persistKey,
  emptyText,
  rowClassName,
  controlsPosition = "bottom",
  controlsLeading,
  externalFilter,
  resetFiltersToken,
}: {
  rows: T[];
  columns: Column<T>[];
  rowKey: (row: T) => string;
  pageSize?: number;
  pageSizeOptions?: number[];
  persistKey?: string;
  emptyText?: string;
  rowClassName?: (row: T) => string;
  controlsPosition?: "top" | "bottom" | "both";
  controlsLeading?: ReactNode;
  // Set a column's filter from outside (e.g. "focus this channel" deep-links here). Applied
  // whenever its value changes; the user can still clear it from the header afterwards.
  externalFilter?: { key: string; value: string } | null;
  // Bump this to clear all column filters — for navigation intents that mean "show me this
  // set" (e.g. the header's "without full history" link), so a stale persisted filter can't
  // hide the rows the user was just sent to see.
  resetFiltersToken?: number;
}) {
  const { t } = useTranslation();
  const initial = loadPersist(persistKey);
  const [sort, setSort] = useState<SortState>(initial.sort);
  const [filters, setFilters] = useState<FilterMap>(initial.filters);
  const [page, setPage] = useState(initial.page);
  const [size, setSize] = useState(initial.size ?? pageSize);
  const [openFilter, setOpenFilter] = useState<string | null>(null);
  // Local text for the editable "jump to page" box (committed on Enter/blur).
  const [pageInput, setPageInput] = useState("1");
  const popRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (persistKey) localStorage.setItem(persistKey, JSON.stringify({ sort, filters, page, size }));
  }, [persistKey, sort, filters, page, size]);

  useEffect(() => {
    if (!externalFilter) return;
    setFilters((f) => ({ ...f, [externalFilter.key]: externalFilter.value }));
    setPage(0);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [externalFilter?.key, externalFilter?.value]);

  // Clear column filters when the reset token advances. The ref starts at 0 (not at the prop)
  // so a navigation that bumped the token still clears even when it remounts this table fresh
  // (e.g. arriving from another page); a plain reload keeps the token at 0 and the effect is a
  // no-op, so the user's persisted filters survive.
  const resetRef = useRef(0);
  useEffect(() => {
    if (resetFiltersToken === undefined || resetFiltersToken === resetRef.current) return;
    resetRef.current = resetFiltersToken;
    setFilters({});
    setPage(0);
  }, [resetFiltersToken]);

  useDismiss(!!openFilter, () => setOpenFilter(null), popRef);

  const filtered = rows.filter((row) =>
    columns.every((col) => {
      const f = col.filter;
      if (!f) return true;
      const val = filters[col.key];
      if (!isActive(val)) return true;
      if (f.kind === "text") return f.get(row).toLowerCase().includes(String(val).toLowerCase());
      if (f.kind === "select") return f.test(row, String(val));
      return f.test(row, val as string[]);
    })
  );

  const sortCol = sort ? columns.find((c) => c.key === sort.key) : undefined;
  const sorted =
    sort && sortCol?.sortValue
      ? [...filtered].sort((a, b) => {
          const av = sortCol.sortValue!(a);
          const bv = sortCol.sortValue!(b);
          const cmp =
            typeof av === "number" && typeof bv === "number"
              ? av - bv
              : String(av).localeCompare(String(bv));
          return sort.dir === "asc" ? cmp : -cmp;
        })
      : filtered;

  // size <= 0 means "All" — one page with every row.
  const allRows = size <= 0;
  const totalPages = allRows ? 1 : Math.max(1, Math.ceil(sorted.length / size));
  const safePage = Math.min(page, totalPages - 1);
  const paged = allRows ? sorted : sorted.slice(safePage * size, safePage * size + size);

  useEffect(() => setPageInput(String(safePage + 1)), [safePage]);
  function commitPageInput() {
    const n = parseInt(pageInput, 10);
    if (!isNaN(n)) setPage(Math.min(totalPages - 1, Math.max(0, n - 1)));
    else setPageInput(String(safePage + 1));
  }

  function toggleSort(key: string) {
    setSort((s) =>
      !s || s.key !== key ? { key, dir: "asc" } : s.dir === "asc" ? { key, dir: "desc" } : null
    );
    setPage(0);
  }
  function applyFilter(key: string, value: string | string[]) {
    setFilters((f) => ({ ...f, [key]: value }));
    setPage(0);
  }

  const align = (a?: "left" | "right" | "center") =>
    a === "right" ? "text-right" : a === "center" ? "text-center" : "text-left";

  function FilterPopover({ col }: { col: Column<T> }) {
    const f = col.filter!;
    const val = filters[col.key];
    return (
      <div
        ref={popRef}
        className="glass-menu absolute left-0 top-full mt-1 z-30 w-52 rounded-xl p-2 animate-[popIn_0.16s_ease]"
      >
        {f.kind === "text" && (
          <input
            autoFocus
            value={(val as string) ?? ""}
            onChange={(e) => applyFilter(col.key, e.target.value)}
            placeholder={col.header}
            className="w-full bg-card border border-border rounded-md px-2 py-1 text-xs outline-none focus:border-accent"
          />
        )}
        {f.kind === "select" && (
          <div className="flex flex-col gap-0.5">
            <button
              onClick={() => applyFilter(col.key, "")}
              className={`text-left text-xs px-2 py-1.5 rounded-md transition ${
                !isActive(val) ? "bg-accent text-accent-fg" : "text-muted hover:bg-card"
              }`}
            >
              {t("datatable.all")}
            </button>
            {f.options.map((o) => (
              <button
                key={o.value}
                onClick={() => applyFilter(col.key, o.value)}
                className={`text-left text-xs px-2 py-1.5 rounded-md transition ${
                  val === o.value ? "bg-accent text-accent-fg" : "text-muted hover:bg-card"
                }`}
              >
                {o.label}
              </button>
            ))}
          </div>
        )}
        {f.kind === "multi" && (
          <div className="flex flex-col gap-0.5 max-h-60 overflow-y-auto">
            {f.options.length === 0 && (
              <span className="text-xs text-muted px-2 py-1">{t("datatable.noOptions")}</span>
            )}
            {f.options.map((o) => {
              const arr = (val as string[]) ?? [];
              const on = arr.includes(o.value);
              return (
                <button
                  key={o.value}
                  onClick={() =>
                    applyFilter(
                      col.key,
                      on ? arr.filter((x) => x !== o.value) : [...arr, o.value]
                    )
                  }
                  className={`flex items-center gap-2 text-left text-xs px-2 py-1.5 rounded-md transition ${
                    on ? "text-fg" : "text-muted hover:bg-card"
                  }`}
                >
                  <span
                    className={`w-3.5 h-3.5 rounded border flex items-center justify-center ${
                      on ? "bg-accent border-accent text-accent-fg" : "border-border"
                    }`}
                  >
                    {on && <X className="w-2.5 h-2.5" />}
                  </span>
                  {o.label}
                </button>
              );
            })}
          </div>
        )}
        {isActive(val) && (
          <button
            onClick={() => applyFilter(col.key, f.kind === "multi" ? [] : "")}
            className="mt-1.5 w-full text-xs text-muted hover:text-accent transition"
          >
            {t("datatable.clear")}
          </button>
        )}
      </div>
    );
  }

  const controls =
    controlsLeading != null || sorted.length > 0 ? (
      <div className="flex items-center justify-between gap-3 my-3 text-sm flex-wrap">
        <div className="flex items-center gap-3 flex-wrap">{controlsLeading}</div>
        <div className="flex items-center gap-3 flex-wrap">
          {totalPages > 1 && (
            <div className="flex items-center gap-2">
              <button
                onClick={() => setPage((p) => Math.max(0, p - 1))}
                disabled={safePage === 0}
                className="glass-card glass-hover flex items-center gap-1 px-3 py-1.5 rounded-lg disabled:opacity-40 transition"
              >
                <ChevronLeft className="w-4 h-4" />
                {t("datatable.pager.prev")}
              </button>
              <span className="text-muted flex items-center gap-1.5">
                {t("datatable.pager.pageLabel")}
                <input
                  type="number"
                  min={1}
                  max={totalPages}
                  value={pageInput}
                  onChange={(e) => setPageInput(e.target.value)}
                  onBlur={commitPageInput}
                  onKeyDown={(e) => e.key === "Enter" && commitPageInput()}
                  aria-label={t("datatable.pager.pageLabel")}
                  className="w-14 bg-card border border-border rounded-md px-1.5 py-1 text-xs text-center tabular-nums outline-none focus:border-accent"
                />
                {t("datatable.pager.ofTotal", { total: totalPages })}
              </span>
              <button
                onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
                disabled={safePage >= totalPages - 1}
                className="glass-card glass-hover flex items-center gap-1 px-3 py-1.5 rounded-lg disabled:opacity-40 transition"
              >
                {t("datatable.pager.next")}
                <ChevronRight className="w-4 h-4" />
              </button>
            </div>
          )}
          {sorted.length > 0 && (
            <label className="flex items-center gap-2 text-muted">
              {t("datatable.rowsPerPage")}
              <select
                value={size}
                onChange={(e) => {
                  setSize(Number(e.target.value));
                  setPage(0);
                }}
                className="bg-card border border-border rounded-md px-1.5 py-1 text-xs outline-none focus:border-accent"
              >
                {pageSizeOptions.map((n) => (
                  <option key={n} value={n}>
                    {n}
                  </option>
                ))}
                <option value={0}>{t("datatable.all")}</option>
              </select>
            </label>
          )}
        </div>
      </div>
    ) : null;

  return (
    <div>
      {(controlsPosition === "top" || controlsPosition === "both") && controls}
      {/* Wide screens: table. The wrapper isn't clipped so header filter popovers can overflow. */}
      <div className="hidden md:block">
        <table className="w-full border-collapse text-sm">
          <thead>
            <tr className="text-muted border-b border-border">
              {columns.map((col) => {
                const sorted_ = sort?.key === col.key;
                const filterOn = isActive(filters[col.key]);
                return (
                  <th
                    key={col.key}
                    style={col.width ? { width: col.width } : undefined}
                    className={`relative font-medium px-2 py-2 whitespace-nowrap ${align(col.align)}`}
                  >
                    <span className="inline-flex items-center gap-1">
                      <button
                        onClick={() => col.sortable && toggleSort(col.key)}
                        className={`inline-flex items-center gap-1 ${
                          col.sortable ? "hover:text-fg cursor-pointer" : "cursor-default"
                        }`}
                      >
                        {col.header}
                        {col.sortable && sorted_ && sort && (
                          sort.dir === "asc" ? (
                            <ArrowUp className="w-3 h-3" />
                          ) : (
                            <ArrowDown className="w-3 h-3" />
                          )
                        )}
                      </button>
                      {col.filter && (
                        <button
                          onClick={() => setOpenFilter((o) => (o === col.key ? null : col.key))}
                          aria-label={t("datatable.filter")}
                          className={`transition ${
                            filterOn ? "text-accent" : "text-muted/60 hover:text-fg"
                          }`}
                        >
                          <ListFilter className="w-3.5 h-3.5" />
                        </button>
                      )}
                    </span>
                    {openFilter === col.key && col.filter && <FilterPopover col={col} />}
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {paged.map((row) => (
              <tr
                key={rowKey(row)}
                className={`border-b border-border/50 hover:bg-card/40 transition ${
                  rowClassName?.(row) ?? ""
                }`}
              >
                {columns.map((col) => (
                  <td
                    key={col.key}
                    style={col.width ? { width: col.width } : undefined}
                    className={`px-2 py-2 align-middle ${col.nowrap ? "whitespace-nowrap" : ""} ${align(col.align)}`}
                  >
                    {col.render(row)}
                  </td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      {/* Narrow screens: a compact card per row — the primary column as the heading, the
          rest flowing in a single wrapping meta line (no full-width label/value gaps). */}
      <div className="md:hidden flex flex-col gap-2">
        {paged.map((row) => (
          <div
            key={rowKey(row)}
            className={`glass-card rounded-xl p-3 ${rowClassName?.(row) ?? ""}`}
          >
            {columns
              .filter((c) => c.cardPrimary)
              .map((col) => (
                <div key={col.key} className="font-medium">
                  {col.render(row)}
                </div>
              ))}
            <div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 mt-2 text-sm">
              {columns
                .filter((c) => !c.hideInCard && !c.cardPrimary)
                .map((col) => (
                  <span key={col.key} className="inline-flex items-center gap-1">
                    {col.cardLabel !== false && (
                      <span className="text-muted text-xs">{col.header}</span>
                    )}
                    {col.render(row)}
                  </span>
                ))}
            </div>
          </div>
        ))}
      </div>

      {paged.length === 0 && (
        <div className="text-muted py-8 text-center text-sm">{emptyText ?? t("datatable.empty")}</div>
      )}

      {(controlsPosition === "bottom" || controlsPosition === "both") && controls}
    </div>
  );
}
