import { useEffect, useState } from "react";
import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";

import type { components } from "../api/schema";
import { useObjectsPage } from "../api/queries";
import { useDebouncedValue } from "../lib/use-debounced-value";
import { focusRing } from "../lib/focus-ring";
import { segmentClass, rowStateClass } from "../lib/class-recipes";
import { formatTimestamp } from "../lib/format-timestamp";
import { useConfig } from "../config/config-context";
import { VisibilityBadge } from "./visibility-badge";
import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Skeleton } from "@/components/ui/skeleton";

type AdminObjectView = components["schemas"]["AdminObjectView"];

const PAGE_SIZES = [25, 50, 100, 200];
const VIS = ["all", "draft", "internal", "public"] as const;
const DEFAULT_SORT = "object_number";
const DEFAULT_LIMIT = 50;

type SortColumn = "object_number" | "object_name" | "updated_at";

const COLUMN_KEYS: Record<SortColumn, string> = {
  object_number: "objects.columns.number",
  object_name: "objects.columns.name",
  updated_at: "objects.columns.updated",
};

export function ObjectsTable() {
  const { t, i18n } = useTranslation();
  const { default_timezone } = useConfig();
  const navigate = useNavigate();
  const { id: selectedId } = useParams();
  const [params, setParams] = useSearchParams();

  const sort = params.get("sort") ?? DEFAULT_SORT;
  const order = (params.get("order") === "desc" ? "desc" : "asc") as "asc" | "desc";
  const visibility = params.get("visibility") ?? "all";
  const limit = Number(params.get("limit")) || DEFAULT_LIMIT;
  const offset = Number(params.get("offset")) || 0;
  const qParam = params.get("q") ?? "";

  const [qText, setQText] = useState(qParam);
  const q = useDebouncedValue(qText, 300);

  // Mirror the search-panel pattern: sync the debounced quick-filter into the URL
  // (setParams is router state, not component setState, so the lint rule allows it).
  // Guard on the URL already matching `q` so re-renders caused by other URL updates
  // (e.g. pagination changing `offset`) don't re-run this and clobber that state.
  useEffect(() => {
    const term = q.trim();

    setParams(
      (prev) => {
        if ((prev.get("q") ?? "") === term) return prev;

        const next = new URLSearchParams(prev);

        if (term) next.set("q", term);
        else next.delete("q");

        next.delete("offset");

        return next;
      },
      { replace: true },
    );
  }, [q, setParams]);

  const { data, isLoading, isError } = useObjectsPage({
    limit,
    offset,
    sort,
    order,
    visibility: visibility === "all" ? undefined : visibility,
    q: q.trim() || undefined,
  });

  const setParam = (mutate: (next: URLSearchParams) => void) =>
    setParams(
      (prev) => {
        const next = new URLSearchParams(prev);
        mutate(next);
        return next;
      },
      { replace: true },
    );

  const toggleSort = (col: SortColumn) =>
    setParam((next) => {
      const curOrder = next.get("order") === "desc" ? "desc" : "asc";
      const curSort = next.get("sort") ?? DEFAULT_SORT;
      const nextOrder = curSort === col && curOrder === "asc" ? "desc" : "asc";

      next.set("sort", col);
      next.set("order", nextOrder);
      next.delete("offset");
    });

  const setVisibility = (value: string) =>
    setParam((next) => {
      if (value === "all") next.delete("visibility");
      else next.set("visibility", value);

      next.delete("offset");
    });

  const setLimit = (value: number) =>
    setParam((next) => {
      next.set("limit", String(value));
      next.delete("offset");
    });

  const goToOffset = (value: number) =>
    setParam((next) => {
      if (value <= 0) next.delete("offset");
      else next.set("offset", String(value));
    });

  const headerCell = (col: SortColumn) => {
    const active = sort === col;
    const ariaSort = active ? (order === "asc" ? "ascending" : "descending") : "none";
    const Icon = !active ? ChevronsUpDown : order === "asc" ? ChevronUp : ChevronDown;

    return (
      <th key={col} scope="col" aria-sort={ariaSort} className="px-3 py-2 text-left font-medium">
        <button
          type="button"
          onClick={() => toggleSort(col)}
          className="flex items-center gap-1 hover:text-foreground"
        >
          {t(COLUMN_KEYS[col])}
          <Icon className="size-3.5 text-muted-foreground" aria-hidden="true" />
        </button>
      </th>
    );
  };

  const total = data?.total ?? 0;
  const from = total === 0 ? 0 : offset + 1;
  const to = Math.min(offset + limit, total);

  const toolbar = (
    <div className="flex flex-wrap items-center gap-2 border-b p-3">
      <Input
        value={qText}
        onChange={(event) => setQText(event.target.value)}
        placeholder={t("objects.filter")}
        aria-label={t("objects.filter")}
        className="max-w-xs"
      />
      <div className="flex gap-1 text-xs">
        {VIS.map((value) => {
          const active = visibility === value;

          return (
            <button
              key={value}
              type="button"
              aria-pressed={active}
              onClick={() => setVisibility(value)}
              className={segmentClass(active, "px-2 py-1")}
            >
              {value === "all" ? t("search.all") : t(`visibility.${value}`)}
            </button>
          );
        })}
      </div>
      <Link to="/objects/new" className={`${buttonVariants({ size: "sm" })} ml-auto`}>
        {t("objects.new")}
      </Link>
    </div>
  );

  const columns = (
    <thead className="border-b bg-muted text-xs text-muted-foreground">
      <tr>
        {headerCell("object_number")}
        {headerCell("object_name")}
        <th scope="col" className="px-3 py-2 text-left font-medium">
          {t("objects.columns.visibility")}
        </th>
        <th scope="col" className="px-3 py-2 text-left font-medium">
          {t("objects.columns.location")}
        </th>
        <th scope="col" className="px-3 py-2 text-right font-medium">
          {t("objects.columns.count")}
        </th>
        {headerCell("updated_at")}
      </tr>
    </thead>
  );

  let body;

  if (isLoading) {
    body = (
      <tbody>
        {Array.from({ length: 8 }).map((_, i) => (
          <tr key={i} className="border-b">
            <td colSpan={6} className="px-3 py-2">
              <Skeleton className="h-5 w-full" />
            </td>
          </tr>
        ))}
      </tbody>
    );
  } else if (isError) {
    body = (
      <tbody>
        <tr>
          <td colSpan={6} role="alert" className="px-3 py-6 text-center text-sm text-destructive">
            {t("objects.loadError")}
          </td>
        </tr>
      </tbody>
    );
  } else if (!data || data.items.length === 0) {
    body = (
      <tbody>
        <tr>
          <td colSpan={6} className="px-3 py-6 text-center text-sm text-muted-foreground">
            {t("objects.empty")}
          </td>
        </tr>
      </tbody>
    );
  } else {
    body = (
      <tbody>
        {data.items.map((item) => {
          const object = item as AdminObjectView;
          const selected = object.id === selectedId;

          return (
            <tr
              key={object.id}
              onClick={() => navigate(`/objects/${object.id}?${params}`)}
              className={`cursor-pointer border-b text-sm ${rowStateClass(selected)}`}
            >
              <td className="px-3 py-2 text-muted-foreground">
                <Link
                  to={`/objects/${object.id}?${params}`}
                  aria-current={selected ? "page" : undefined}
                  onClick={(event) => event.stopPropagation()}
                  className={`${focusRing} rounded-sm hover:underline`}
                >
                  {object.object_number}
                </Link>
              </td>
              <td className="px-3 py-2 font-medium">{object.object_name}</td>
              <td className="px-3 py-2">
                <VisibilityBadge visibility={object.visibility} />
              </td>
              <td className="px-3 py-2 text-muted-foreground">{object.current_location ?? "—"}</td>
              <td className="px-3 py-2 text-right tabular-nums">{object.number_of_objects}</td>
              <td className="px-3 py-2 text-muted-foreground">
                {formatTimestamp(object.updated_at, default_timezone, i18n.language)}
              </td>
            </tr>
          );
        })}
      </tbody>
    );
  }

  return (
    <div className="flex h-full flex-col">
      {toolbar}
      <div className="flex-1 overflow-auto">
        <table className="w-full border-collapse" aria-busy={isLoading || undefined}>
          <caption className="sr-only" aria-live="polite">
            {isLoading ? t("common.loading") : t("objects.tableLabel")}
          </caption>
          {columns}
          {body}
        </table>
      </div>
      <div className="flex items-center justify-between gap-2 border-t px-3 py-2 text-xs text-muted-foreground">
        <label className="flex items-center gap-1">
          <span>{t("objects.pageSize")}</span>
          <select
            value={limit}
            onChange={(event) => setLimit(Number(event.target.value))}
            aria-label={t("objects.pageSize")}
            className="rounded-md border bg-background px-1 py-0.5"
          >
            {PAGE_SIZES.map((size) => (
              <option key={size} value={size}>
                {size}
              </option>
            ))}
          </select>
        </label>
        <span>
          {from}–{to} {t("objects.of")} {total}
        </span>
        <span className="flex gap-2">
          <Button
            variant="ghost"
            size="sm"
            disabled={offset === 0}
            onClick={() => goToOffset(Math.max(0, offset - limit))}
          >
            {t("objects.prev")}
          </Button>
          <Button
            variant="ghost"
            size="sm"
            disabled={offset + limit >= total}
            onClick={() => goToOffset(offset + limit)}
          >
            {t("objects.next")}
          </Button>
        </span>
      </div>
    </div>
  );
}
