import { useEffect, useMemo, useRef, useState } from "react";
import { AlertIcon, ChevronDownIcon, FilterIcon, SearchIcon, SparkleIcon } from "../ui/Icons";
import { CustomSelect } from "../ui/CustomSelect";
import { Spinner } from "../ui/Spinner";
import { Badge } from "../ui/Badge";

const COLUMNS = [
  { key: "title", label: "Título", thClass: "w-[35%]" },
  { key: "journal", label: "Revista / Fuente", thClass: "w-[28%]", tdClass: "break-words" },
  { key: "publication_year", label: "Año", thClass: "w-16" },
  { key: "doi", label: "DOI" },
  { key: "type", label: "Tipo", thClass: "w-20" },
];

const PAGE_SIZE = 15;
const EMPTY_SELECTION = new Set();

function SortIcon({ active, direction }) {
  const path =
    direction === "asc" || !active ? "M6 8L3 5h6z" : "M6 4l3 3H3z";
  return (
    <svg
      width="12"
      height="12"
      viewBox="0 0 12 12"
      fill="none"
      className={`ml-1 ${active ? "opacity-100" : "opacity-30"}`}
      aria-hidden
    >
      <path d={path} fill="currentColor" />
    </svg>
  );
}

function sortPublications(rows, key, direction) {
  const sorted = [...rows].sort((a, b) => {
    const va = a[key];
    const vb = b[key];
    const cmp =
      typeof va === "string" ? va.localeCompare(vb) : (va ?? 0) - (vb ?? 0);
    return direction === "asc" ? cmp : -cmp;
  });
  return sorted;
}

/**
 * Tri-state checkbox. We can't express `indeterminate` via React props, so
 * we set it imperatively on the DOM node whenever the flag changes.
 */
function TriStateCheckbox({ checked, indeterminate = false, onChange, ariaLabel }) {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) ref.current.indeterminate = indeterminate && !checked;
  }, [indeterminate, checked]);
  return (
    <input
      ref={ref}
      type="checkbox"
      checked={checked}
      onChange={onChange}
      aria-label={ariaLabel}
      className="h-4 w-4 cursor-pointer accent-brand-accent"
    />
  );
}

/**
 * Publications table. UI-state (filter, sort, pagination) lives here; the
 * *selection* set is lifted to the parent so export / bulk actions can see
 * it. Data, loading and error states are also driven by the parent so
 * retries and toasts can be handled in one place.
 *
 * Selection semantics:
 *   - The master checkbox toggles only the rows on the current page.
 *   - Selection is stored by ID in the parent and persists across pages,
 *     filters and sorts so the user can select page by page.
 */
export function PublicationsTable({
  publications,
  loading = false,
  error = null,
  onRetry,
  selectedIds = EMPTY_SELECTION,
  onSelectedIdsChange,
  isAuthenticated = false,
}) {
  const [filter, setFilter] = useState("");
  const [sortKey, setSortKey] = useState("publication_year");
  const [sortDir, setSortDir] = useState("desc");
  const [page, setPage] = useState(1);

  const [filtersOpen, setFiltersOpen] = useState(false);
  const [yearFrom, setYearFrom] = useState("");
  const [yearTo, setYearTo] = useState("");

  const availableYears = useMemo(() => {
    const years = publications
      .map((p) => p.publication_year)
      .filter((y) => typeof y === "number" && Number.isFinite(y));
    if (years.length === 0) return [];
    const min = Math.min(...years);
    const max = Math.max(...years, new Date().getFullYear());
    const list = [];
    for (let y = max; y >= min; y -= 1) list.push(y);
    return list;
  }, [publications]);

  const hasYearFilter = yearFrom !== "" || yearTo !== "";

  useEffect(() => {
    if (availableYears.length === 0) return;
    if (yearFrom && !availableYears.includes(Number(yearFrom))) setYearFrom("");
    if (yearTo && !availableYears.includes(Number(yearTo))) setYearTo("");
  }, [availableYears, yearFrom, yearTo]);

  const filtered = useMemo(() => {
    const needle = filter.trim().toLowerCase();
    let rows = publications;

    if (needle) {
      rows = rows.filter(
        (p) =>
          (p.title ?? "").toLowerCase().includes(needle) ||
          (p.journal ?? "").toLowerCase().includes(needle) ||
          String(p.publication_year ?? "").includes(needle) ||
          (p.doi ?? "").toLowerCase().includes(needle),
      );
    }

    if (hasYearFilter) {
      const from = yearFrom ? Number(yearFrom) : null;
      const to = yearTo ? Number(yearTo) : null;
      rows = rows.filter((p) => {
        const year = p.publication_year;
        if (typeof year !== "number" || !Number.isFinite(year)) return false;
        if (from !== null && year < from) return false;
        if (to !== null && year > to) return false;
        return true;
      });
    }

    return sortPublications(rows, sortKey, sortDir);
  }, [publications, filter, yearFrom, yearTo, hasYearFilter, sortKey, sortDir]);

  const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const currentPage = Math.min(page, totalPages);

  const pageRows = useMemo(() => {
    const start = (currentPage - 1) * PAGE_SIZE;
    return filtered.slice(start, start + PAGE_SIZE);
  }, [filtered, currentPage]);

  const pageSelectionStats = useMemo(() => {
    if (pageRows.length === 0) {
      return { allChecked: false, anyChecked: false };
    }
    let count = 0;
    for (const pub of pageRows) {
      if (selectedIds.has(pub.id)) count += 1;
    }
    return {
      allChecked: count === pageRows.length,
      anyChecked: count > 0,
    };
  }, [pageRows, selectedIds]);

  function toggleSort(key) {
    if (sortKey === key) {
      setSortDir((d) => (d === "asc" ? "desc" : "asc"));
      setPage(1);
    } else {
      setSortKey(key);
      setSortDir("desc");
      setPage(1);
    }
  }

  function emit(nextSet) {
    onSelectedIdsChange?.(nextSet);
  }

  function toggleRow(id) {
    const next = new Set(selectedIds);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    emit(next);
  }

  function toggleCurrentPage() {
    const next = new Set(selectedIds);
    if (pageSelectionStats.allChecked) {
      for (const pub of pageRows) next.delete(pub.id);
    } else {
      for (const pub of pageRows) next.add(pub.id);
    }
    emit(next);
  }

  function handleYearFromChange(value) {
    setYearFrom(value);
    // Si el usuario elige un "Desde" mayor que el "Hasta" actual,
    // auto-corregimos el "Hasta" para preservar un rango coherente.
    if (value && yearTo && Number(value) > Number(yearTo)) {
      setYearTo(value);
    }
    setPage(1);
  }

  function handleYearToChange(value) {
    setYearTo(value);
    if (value && yearFrom && Number(value) < Number(yearFrom)) {
      setYearFrom(value);
    }
    setPage(1);
  }

  function clearYearFilter() {
    setYearFrom("");
    setYearTo("");
    setPage(1);
  }

  const pageStart =
    filtered.length === 0 ? 0 : (currentPage - 1) * PAGE_SIZE + 1;
  const pageEnd = Math.min(currentPage * PAGE_SIZE, filtered.length);

  const yearFilterSummary = hasYearFilter
    ? yearFrom && yearTo && yearFrom === yearTo
      ? `Año: ${yearFrom}`
      : `Años: ${yearFrom || "…"} – ${yearTo || "…"}`
    : null;

  return (
    <section className="overflow-hidden rounded-2xl border border-surface-border/60 bg-surface-primary">
      {/* Toolbar */}
      <div className="border-b border-surface-border/60">
        <div className="flex flex-wrap items-center justify-between gap-3 px-5 py-4">
          <div>
            <h3 className="text-base font-medium text-ink-primary">
              Publicaciones
            </h3>
            <p className="mt-0.5 text-xs text-ink-tertiary">
              {filtered.length} de {publications.length} resultados
              {yearFilterSummary && (
                <>
                  {" · "}
                  <span className="text-ink-secondary">{yearFilterSummary}</span>
                </>
              )}
              {selectedIds.size > 0 && (
                <>
                  {" · "}
                  <span className="font-medium text-brand-accent">
                    {selectedIds.size} seleccionada
                    {selectedIds.size === 1 ? "" : "s"}
                  </span>
                </>
              )}
            </p>
          </div>
          <div className="flex w-full flex-wrap items-center gap-2 sm:w-auto">
            <button
              type="button"
              onClick={() => setFiltersOpen((o) => !o)}
              aria-expanded={filtersOpen}
              aria-controls="pubs-advanced-filters"
              className={`inline-flex items-center gap-1.5 rounded-lg border px-3 py-2 text-[13px] font-medium transition-colors ${
                filtersOpen || hasYearFilter
                  ? "border-brand-accent/50 bg-brand-accent/10 text-brand-accent"
                  : "border-surface-border-strong bg-surface-secondary text-ink-secondary hover:bg-surface-primary"
              }`}
            >
              <FilterIcon />
              Filtros
              {hasYearFilter && (
                <span
                  className="ml-0.5 inline-block h-1.5 w-1.5 rounded-full bg-brand-accent"
                  aria-hidden
                />
              )}
              <ChevronDownIcon
                className={`transition-transform ${filtersOpen ? "rotate-180" : ""}`}
              />
            </button>
            <div className="relative w-full sm:w-auto">
              <input
                type="text"
                placeholder="Filtrar publicaciones..."
                value={filter}
                onChange={(e) => {
                  setFilter(e.target.value);
                  setPage(1);
                }}
                className="w-full rounded-lg border border-surface-border-strong bg-surface-secondary py-2 pl-9 pr-3.5 text-[13px] text-ink-primary outline-none focus:border-brand-accent sm:w-[220px]"
              />
              <span className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-ink-tertiary/70">
                <SearchIcon />
              </span>
            </div>
          </div>
        </div>

        {filtersOpen && (
          <div
            id="pubs-advanced-filters"
            className="flex flex-wrap items-end gap-4 border-t border-surface-border/60 bg-surface-secondary/40 px-5 py-3"
          >
            <div className="flex flex-col gap-1">
              <label
                htmlFor="year-from"
                className="text-[11px] font-medium uppercase tracking-wide text-ink-tertiary"
              >
                Desde año
              </label>
              <CustomSelect
                id="year-from"
                value={yearFrom}
                onChange={handleYearFromChange}
                disabled={availableYears.length === 0}
                options={availableYears.map((y) => ({
                  value: String(y),
                  label: String(y),
                }))}
              />
            </div>
            <div className="flex flex-col gap-1">
              <label
                htmlFor="year-to"
                className="text-[11px] font-medium uppercase tracking-wide text-ink-tertiary"
              >
                Hasta año
              </label>
              <CustomSelect
                id="year-to"
                value={yearTo}
                onChange={handleYearToChange}
                disabled={availableYears.length === 0}
                options={availableYears.map((y) => ({
                  value: String(y),
                  label: String(y),
                }))}
              />
            </div>
            {hasYearFilter && (
              <button
                type="button"
                onClick={clearYearFilter}
                className="mb-[2px] rounded-md px-2.5 py-1.5 text-xs font-medium text-ink-tertiary transition-colors hover:bg-surface-primary hover:text-ink-primary"
              >
                Limpiar rango
              </button>
            )}
            {availableYears.length === 0 && (
              <p className="mb-[2px] text-xs text-ink-tertiary">
                Aún no hay años disponibles.
              </p>
            )}
          </div>
        )}
      </div>

      {/* Body */}
      <div className="overflow-x-auto">
        {error ? (
          <ErrorState error={error} onRetry={onRetry} />
        ) : loading ? (
          <LoadingState />
        ) : (
          <>
            <div className="md:hidden">
              {filtered.length === 0 ? (
                <p className="p-8 text-center text-sm text-ink-tertiary">
                  No se encontraron publicaciones con los filtros aplicados.
                </p>
              ) : (
                <>
                  <div className="border-b border-surface-border/60 bg-surface-secondary px-4 py-2.5">
                    <TriStateCheckbox
                      checked={pageSelectionStats.allChecked}
                      indeterminate={pageSelectionStats.anyChecked}
                      onChange={toggleCurrentPage}
                      ariaLabel="Seleccionar todas las publicaciones de esta página"
                    />
                  </div>
                  <div>
                    {pageRows.map((pub, i) => {
                      const isSelected = selectedIds.has(pub.id);
                      return (
                        <article
                          key={pub.id}
                          className={`px-4 py-3.5 transition-colors ${
                            isSelected
                              ? "bg-tag-article-bg/70 hover:bg-tag-article-bg"
                              : "hover:bg-surface-secondary/70"
                          } ${
                            i < pageRows.length - 1
                              ? "border-b border-surface-border/60"
                              : ""
                          }`}
                        >
                          <div className="mb-2 flex items-start gap-2.5">
                            <TriStateCheckbox
                              checked={isSelected}
                              onChange={() => toggleRow(pub.id)}
                              ariaLabel={`Seleccionar publicación ${pub.title}`}
                            />
                            <div className="min-w-0 flex-1">
                              <div className="flex flex-wrap items-start gap-1.5">
                                {isAuthenticated && pub.downloaded_by_me === false && (
                                  <span
                                    title="No descargada aún por ti"
                                    className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 rounded-full bg-brand-accent/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent"
                                  >
                                    <SparkleIcon size={9} />
                                    Nuevo
                                  </span>
                                )}
                                <p className="text-[14px] font-medium leading-relaxed text-ink-primary">
                                  {pub.title}
                                </p>
                              </div>
                            </div>
                          </div>
                          <div className="space-y-1.5 pl-6.5 text-[12px] text-ink-secondary">
                            <p>
                              <span className="font-medium text-ink-primary">Revista:</span>{" "}
                              {pub.journal || "—"}
                            </p>
                            <p>
                              <span className="font-medium text-ink-primary">Año:</span>{" "}
                              {pub.publication_year ?? "—"}
                            </p>
                            <p>
                              <span className="font-medium text-ink-primary">DOI:</span>{" "}
                              {pub.doi ? (
                                <a
                                  href={`https://doi.org/${pub.doi}`}
                                  target="_blank"
                                  rel="noopener noreferrer"
                                  className="break-all font-mono text-[11px] text-brand-accent hover:underline"
                                >
                                  {pub.doi}
                                </a>
                              ) : (
                                <span className="font-mono text-[11px] text-ink-tertiary">
                                  —
                                </span>
                              )}
                            </p>
                            <div>
                              <Badge type={pub.type} />
                            </div>
                          </div>
                        </article>
                      );
                    })}
                  </div>
                </>
              )}
            </div>

            <table className="hidden w-full border-collapse md:table">
              <thead>
                <tr className="bg-surface-secondary">
                  <th
                    scope="col"
                    className="w-10 border-b border-surface-border/60 px-4 py-2.5 text-left"
                    onClick={(e) => e.stopPropagation()}
                  >
                    <TriStateCheckbox
                      checked={pageSelectionStats.allChecked}
                      indeterminate={pageSelectionStats.anyChecked}
                      onChange={toggleCurrentPage}
                      ariaLabel="Seleccionar todas las publicaciones de esta página"
                    />
                  </th>
                  {COLUMNS.map((col) => (
                    <th
                      key={col.key}
                      onClick={() => toggleSort(col.key)}
                      className={`select-none border-b border-surface-border/60 px-4 py-2.5 text-left text-xs font-medium tracking-wide text-ink-secondary${col.thClass ? ` ${col.thClass}` : ""}`}
                    >
                      <span className="flex cursor-pointer items-center">
                        {col.label.toUpperCase()}
                        <SortIcon
                          active={sortKey === col.key}
                          direction={sortDir}
                        />
                      </span>
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {filtered.length === 0 ? (
                  <tr>
                    <td
                      colSpan={COLUMNS.length + 1}
                      className="p-10 text-center text-sm text-ink-tertiary"
                    >
                      No se encontraron publicaciones con los filtros aplicados.
                    </td>
                  </tr>
                ) : (
                  pageRows.map((pub, i) => {
                    const isSelected = selectedIds.has(pub.id);
                    return (
                      <tr
                        key={pub.id}
                        className={`transition-colors ${
                          isSelected
                            ? "bg-tag-article-bg/70 hover:bg-tag-article-bg"
                            : "hover:bg-surface-secondary/70"
                        } ${
                          i < pageRows.length - 1
                            ? "border-b border-surface-border/60"
                            : ""
                        }`}
                      >
                        <td
                          className="w-10 cursor-pointer px-4 py-3.5"
                          onClick={(e) => {
                            e.stopPropagation();
                            toggleRow(pub.id);
                          }}
                        >
                          <TriStateCheckbox
                            checked={isSelected}
                            onChange={() => toggleRow(pub.id)}
                            ariaLabel={`Seleccionar publicación ${pub.title}`}
                          />
                        </td>
                        <td className="max-w-[280px] px-4 py-3.5 text-[13px] font-medium leading-relaxed text-ink-primary">
                          <span className="flex flex-wrap items-start gap-1.5">
                            {isAuthenticated && pub.downloaded_by_me === false && (
                              <span
                                title="No descargada aún por ti"
                                className="mt-0.5 inline-flex shrink-0 items-center gap-0.5 rounded-full bg-brand-accent/10 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent"
                              >
                                <SparkleIcon size={9} />
                                Nuevo
                              </span>
                            )}
                            {pub.title}
                          </span>
                        </td>
                        <td className="px-4 py-3.5 text-[13px] text-ink-secondary">
                          {pub.journal || "—"}
                        </td>
                        <td className="whitespace-nowrap px-4 py-3.5 text-[13px] font-medium text-ink-primary">
                          {pub.publication_year ?? "—"}
                        </td>
                        <td className="px-4 py-3.5">
                          {pub.doi ? (
                            <a
                              href={`https://doi.org/${pub.doi}`}
                              target="_blank"
                              rel="noopener noreferrer"
                              onClick={(e) => e.stopPropagation()}
                              className="whitespace-nowrap font-mono text-xs text-brand-accent hover:underline"
                            >
                              {pub.doi}
                            </a>
                          ) : (
                            <span className="whitespace-nowrap font-mono text-xs text-ink-tertiary">
                              —
                            </span>
                          )}
                        </td>
                        <td className="px-4 py-3.5">
                          <Badge type={pub.type} />
                        </td>
                      </tr>
                    );
                  })
                )}
              </tbody>
            </table>
          </>
        )}
      </div>

      {/* Pagination */}
      {!loading && !error && filtered.length > 0 && (
        <PaginationBar
          page={currentPage}
          totalPages={totalPages}
          pageStart={pageStart}
          pageEnd={pageEnd}
          total={filtered.length}
          onPrev={() => setPage((p) => Math.max(1, Math.min(p, totalPages) - 1))}
          onNext={() => setPage((p) => Math.min(totalPages, Math.min(p, totalPages) + 1))}
        />
      )}
    </section>
  );
}

function PaginationBar({
  page,
  totalPages,
  pageStart,
  pageEnd,
  total,
  onPrev,
  onNext,
}) {
  const hasPrev = page > 1;
  const hasNext = page < totalPages;
  const isSinglePage = totalPages <= 1;
  const noun = total === 1 ? "publicación" : "publicaciones";
  const visibleCount = pageEnd - pageStart + 1;

  return (
    <div className="flex flex-wrap items-center justify-between gap-3 border-t border-surface-border/60 px-5 py-3">
      {isSinglePage ? (
        // Caso "todo entra en una página": evitamos el "del X al Y"
        // porque coincide con el total y resulta ruidoso. Un simple
        // "Mostrando N de N publicaciones" comunica lo mismo con menos
        // palabras.
        <p className="text-xs text-ink-tertiary">
          Mostrando{" "}
          <span className="font-medium text-ink-secondary">{visibleCount}</span>{" "}
          de <span className="font-medium text-ink-secondary">{total}</span>{" "}
          {noun}
        </p>
      ) : (
        <p className="text-xs text-ink-tertiary">
          Mostrando del{" "}
          <span className="font-medium text-ink-secondary">{pageStart}</span>{" "}
          al <span className="font-medium text-ink-secondary">{pageEnd}</span>{" "}
          de un total de{" "}
          <span className="font-medium text-ink-secondary">{total}</span> {noun}
        </p>
      )}
      {!isSinglePage && (
        <div className="flex items-center gap-2">
          <button
            type="button"
            onClick={onPrev}
            disabled={!hasPrev}
            className="inline-flex items-center rounded-md border border-surface-border-strong bg-surface-primary px-3 py-1.5 text-xs font-medium text-ink-secondary transition-colors enabled:hover:bg-surface-secondary disabled:cursor-not-allowed disabled:opacity-40"
          >
            Anterior
          </button>
          <span className="text-xs text-ink-tertiary">
            Página <span className="font-medium text-ink-primary">{page}</span>{" "}
            de <span className="font-medium text-ink-primary">{totalPages}</span>
          </span>
          <button
            type="button"
            onClick={onNext}
            disabled={!hasNext}
            className="inline-flex items-center rounded-md border border-surface-border-strong bg-surface-primary px-3 py-1.5 text-xs font-medium text-ink-secondary transition-colors enabled:hover:bg-surface-secondary disabled:cursor-not-allowed disabled:opacity-40"
          >
            Siguiente
          </button>
        </div>
      )}
    </div>
  );
}

function LoadingState() {
  return (
    <div className="flex flex-col items-center justify-center gap-3 py-16 text-ink-tertiary">
      <Spinner size={22} />
      <p className="text-sm">Cargando publicaciones…</p>
    </div>
  );
}

function ErrorState({ error, onRetry }) {
  return (
    <div className="flex flex-col items-center justify-center gap-3 px-6 py-16 text-center">
      <span className="text-ink-danger">
        <AlertIcon size={28} />
      </span>
      <div>
        <p className="text-sm font-medium text-ink-primary">
          No se pudieron cargar las publicaciones
        </p>
        <p className="mt-1 text-xs text-ink-tertiary">
          {error?.message ?? "Error desconocido."}
        </p>
      </div>
      {onRetry && (
        <button
          type="button"
          onClick={onRetry}
          className="mt-1 inline-flex items-center gap-1.5 rounded-md bg-brand-primary px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-brand-primary-hover"
        >
          Reintentar
        </button>
      )}
    </div>
  );
}
