import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import type { ListResult, ObjectInstanceRecord } from "../api/schemas";
import type { ObjectFilters } from "../api/service";
import { useCursorPager } from "../lib/cursor";
import { objectTypeLabel, truncateMiddle } from "../lib/format";
import { withRunParam } from "../lib/run";
import { CopyableValue } from "./CopyableValue";
import { CursorPagerControls } from "./CursorPagerControls";
import { DataTable, type Column } from "./DataTable";
import { StatusPill } from "./StatusPill";

export interface ObjectFilterValues {
  type: string;
  result: string;
  reason: string;
  rejectedOnly: boolean;
  q: string;
}
export const EMPTY_OBJECT_FILTERS: ObjectFilterValues = {
  type: "",
  result: "",
  reason: "",
  rejectedOnly: false,
  q: "",
};

/** Read object filters from the page URL (standalone pages sync them there). */
export function objectFiltersFromParams(params: URLSearchParams): ObjectFilterValues {
  return {
    type: params.get("type") ?? "",
    result: params.get("result") ?? "",
    reason: params.get("reason") ?? "",
    rejectedOnly: params.get("rejected") === "true",
    q: params.get("q") ?? "",
  };
}

/** Write object filters into a URLSearchParams copy, preserving other params. */
export function objectFiltersToParams(
  next: ObjectFilterValues,
  base: URLSearchParams,
): URLSearchParams {
  const params = new URLSearchParams(base);
  const write = (key: string, value: string) => {
    if (value) params.set(key, value);
    else params.delete(key);
  };
  write("type", next.type);
  write("result", next.result);
  write("reason", next.reason);
  write("q", next.q);
  if (next.rejectedOnly) params.set("rejected", "true");
  else params.delete("rejected");
  return params;
}

/** Report-stream object type vocabulary (backend also accepts mft/cer/asa aliases). */
const TYPE_OPTIONS = ["roa", "manifest", "crl", "certificate", "aspa", "gbr"];
const RESULT_OPTIONS = ["ok", "error", "skipped"];

/**
 * Filter bar + paginated object table. The `fetcher` determines the scope
 * (global / repo / publication point / issues). Filter state is controlled
 * by the caller so standalone pages can sync it to the URL.
 */
export function ObjectsTable({
  runId,
  queryKeyScope,
  fetcher,
  filters,
  onFiltersChange,
  showReason = true,
}: {
  runId: string;
  queryKeyScope: string;
  fetcher: (filters: ObjectFilters) => Promise<ListResult<ObjectInstanceRecord>>;
  filters: ObjectFilterValues;
  onFiltersChange: (next: ObjectFilterValues) => void;
  showReason?: boolean;
}) {
  const navigate = useNavigate();
  const [qDraft, setQDraft] = useState(filters.q);

  // Keep the draft input in sync when filters change externally (URL nav).
  useEffect(() => {
    setQDraft(filters.q);
  }, [filters.q]);

  // Auto-apply the URI draft after a short typing pause (Enter/blur still work).
  useEffect(() => {
    const trimmed = qDraft.trim();
    if (trimmed === filters.q) return;
    const timer = setTimeout(() => onFiltersChange({ ...filters, q: trimmed }), 400);
    return () => clearTimeout(timer);
  }, [qDraft, filters, onFiltersChange]);

  const filterKey = JSON.stringify([runId, filters.type, filters.result, filters.reason, filters.rejectedOnly, filters.q]);
  const pager = useCursorPager(filterKey);

  const query = useQuery({
    queryKey: ["objects", queryKeyScope, runId, filters, pager.cursor],
    queryFn: () =>
      fetcher({
        limit: 50,
        cursor: pager.cursor,
        type: filters.type || undefined,
        result: filters.result || undefined,
        rejected: filters.rejectedOnly || undefined,
        reason: filters.reason || undefined,
        q: filters.q || undefined,
      }),
    placeholderData: (prev) => prev,
  });

  const columns: Column<ObjectInstanceRecord>[] = useMemo(
    () => [
      {
        key: "type",
        header: "Type",
        render: (obj) => <span className="chip">{objectTypeLabel(obj.objectType)}</span>,
      },
      {
        key: "uri",
        header: "URI",
        render: (obj) => <CopyableValue value={obj.uri} max={64} label="object URI" />,
      },
      {
        key: "sha256",
        header: "SHA-256",
        render: (obj) => (
          <span className="mono text-muted" title={obj.sha256 ?? undefined}>
            {obj.sha256 ? truncateMiddle(obj.sha256, 18) : "—"}
          </span>
        ),
      },
      {
        key: "source",
        header: "Source",
        render: (obj) => <StatusPill status={obj.sourceSection} />,
      },
      {
        key: "result",
        header: "Result",
        // Raw `result` value — the Result filter options must match what the
        // column displays (rejection is visible via the reason column/filter).
        render: (obj) => <StatusPill status={obj.result} />,
      },
      ...(showReason
        ? [
            {
              key: "reason",
              header: "Reject reason",
              render: (obj: ObjectInstanceRecord) =>
                obj.rejectReason ? (
                  <span className="mono text-small" title={obj.rejectReason}>
                    {truncateMiddle(obj.rejectReason, 60)}
                  </span>
                ) : (
                  <span className="text-faint">—</span>
                ),
            } satisfies Column<ObjectInstanceRecord>,
          ]
        : []),
    ],
    [showReason],
  );

  const submitQ = () => {
    if (qDraft.trim() !== filters.q) {
      onFiltersChange({ ...filters, q: qDraft.trim() });
    }
  };

  return (
    <div>
      <div className="panel-header filter-bar" role="search">
        <div className="filter-field">
          <label htmlFor={`${queryKeyScope}-type`}>Type</label>
          <select
            id={`${queryKeyScope}-type`}
            value={filters.type}
            onChange={(e) => onFiltersChange({ ...filters, type: e.target.value })}
          >
            <option value="">all types</option>
            {TYPE_OPTIONS.map((t) => (
              <option key={t} value={t}>
                {objectTypeLabel(t)}
              </option>
            ))}
          </select>
        </div>
        <div className="filter-field">
          <label htmlFor={`${queryKeyScope}-result`}>Result</label>
          <select
            id={`${queryKeyScope}-result`}
            value={filters.result}
            onChange={(e) => onFiltersChange({ ...filters, result: e.target.value })}
          >
            <option value="">all results</option>
            {RESULT_OPTIONS.map((r) => (
              <option key={r} value={r}>
                {r}
              </option>
            ))}
          </select>
        </div>
        <div className="filter-field">
          <label htmlFor={`${queryKeyScope}-reason`}>Reason contains</label>
          <input
            id={`${queryKeyScope}-reason`}
            type="search"
            className="mono"
            value={filters.reason}
            onChange={(e) => onFiltersChange({ ...filters, reason: e.target.value })}
            placeholder="substring…"
          />
        </div>
        <div className="filter-field">
          <label htmlFor={`${queryKeyScope}-q`}>URI contains</label>
          <input
            id={`${queryKeyScope}-q`}
            type="search"
            className="mono"
            value={qDraft}
            onChange={(e) => setQDraft(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") submitQ();
            }}
            onBlur={submitQ}
            placeholder="substring… (applies as you type)"
          />
        </div>
        <label className="filter-check">
          <input
            type="checkbox"
            checked={filters.rejectedOnly}
            onChange={(e) => onFiltersChange({ ...filters, rejectedOnly: e.target.checked })}
          />
          Rejected only
        </label>
        <span className="text-faint text-small" style={{ marginLeft: "auto" }}>
          server-side filters · URI search applies as you type
        </span>
      </div>

      <DataTable
        columns={columns}
        rows={query.data?.items ?? []}
        rowKey={(obj) => obj.objectInstanceId}
        loading={query.isPending}
        error={query.isError ? query.error : undefined}
        onRetry={() => query.refetch()}
        emptyTitle="No objects match"
        emptyHint="Try relaxing the filters."
        caption="RPKI objects"
        onRowClick={(obj) =>
          navigate(withRunParam(`/objects/${encodeURIComponent(obj.objectInstanceId)}`, runId))
        }
      />
      <CursorPagerControls
        page={pager.page}
        canPrev={pager.canPrev}
        hasNext={query.data?.nextCursor != null}
        loading={query.isFetching}
        onPrev={pager.goPrev}
        onNext={() => pager.goNext(query.data?.nextCursor ?? null)}
        itemCount={query.data?.items.length}
      />
    </div>
  );
}
