import { useQuery } from "@tanstack/react-query";
import { ChevronRight, Database, FileCode2, GitBranch, Loader2, PanelLeftClose, PanelRightClose } from "lucide-react";
import type { Dispatch, ReactNode, SetStateAction } from "react";
import { useMemo } from "react";
import { CopyableValue } from "../../components/common/CopyableValue";
import { CursorPager } from "../../components/common/CursorPager";
import { advancePage, previousPage, type PageState } from "../../components/common/cursorPaging";
import { createEmptyPageState, type RepositoryBrowserState } from "./repositoryBrowserState";
import {
  getLatestRun,
  listObjectsForPublicationPoint,
  listPublicationPointsForRepo,
  listRepos,
  type ObjectInstanceRecord,
  type PublicationPointRecord,
  type RepositoryRecord
} from "../../api/queryService";
import { normalizeApiError } from "../../api/client";

const REPO_PAGE_SIZE = 50;
const PP_PAGE_SIZE = 50;
const OBJECT_PAGE_SIZE = 50;

interface RepositoriesPageProps {
  browserState: RepositoryBrowserState;
  onBrowserStateChange: Dispatch<SetStateAction<RepositoryBrowserState>>;
  onOpenObject?: (objectInstanceId: string) => void;
}

export function RepositoriesPage({ browserState, onBrowserStateChange, onOpenObject }: RepositoriesPageProps) {
  const {
    selectedRepoId,
    selectedPpId,
    repoFilter,
    ppFilter,
    objectFilter,
    repoPage,
    ppPage,
    objectPage,
    isRepoPanelCollapsed,
    isPpPanelCollapsed
  } = browserState;
  const setRepoFilter = (value: string) => onBrowserStateChange((state) => ({ ...state, repoFilter: value }));
  const setPpFilter = (value: string) => onBrowserStateChange((state) => ({ ...state, ppFilter: value }));
  const setObjectFilter = (value: string) => onBrowserStateChange((state) => ({ ...state, objectFilter: value }));
  const setRepoPage = (updater: SetStateAction<PageState>) => {
    onBrowserStateChange((state) => ({
      ...state,
      repoPage: typeof updater === "function" ? updater(state.repoPage) : updater
    }));
  };
  const setPpPage = (updater: SetStateAction<PageState>) => {
    onBrowserStateChange((state) => ({
      ...state,
      ppPage: typeof updater === "function" ? updater(state.ppPage) : updater
    }));
  };
  const setObjectPage = (updater: SetStateAction<PageState>) => {
    onBrowserStateChange((state) => ({
      ...state,
      objectPage: typeof updater === "function" ? updater(state.objectPage) : updater
    }));
  };
  const latestRunQuery = useQuery({
    queryKey: ["repositories-latest-run"],
    queryFn: getLatestRun
  });
  const runId = latestRunQuery.data?.data.runId ?? "latest";
  const reposQuery = useQuery({
    queryKey: ["repositories", runId, repoPage.cursor],
    queryFn: () => listRepos(runId, REPO_PAGE_SIZE, repoPage.cursor),
    enabled: Boolean(latestRunQuery.data?.data.runId)
  });

  const repos = useMemo(() => reposQuery.data?.data ?? [], [reposQuery.data?.data]);
  const filteredRepos = useMemo(() => filterRepositories(repos, repoFilter), [repoFilter, repos]);
  const selectedRepo = selectedRepoId ? repos.find((repo) => repo.repoId === selectedRepoId) ?? null : null;
  const ppsQuery = useQuery({
    queryKey: ["repository-publication-points", runId, selectedRepo?.repoId ?? "", ppPage.cursor],
    queryFn: () => listPublicationPointsForRepo(runId, selectedRepo?.repoId ?? "", PP_PAGE_SIZE, ppPage.cursor),
    enabled: Boolean(selectedRepo?.repoId)
  });

  const publicationPoints = useMemo(() => ppsQuery.data?.data ?? [], [ppsQuery.data?.data]);
  const filteredPublicationPoints = useMemo(() => filterPublicationPoints(publicationPoints, ppFilter), [ppFilter, publicationPoints]);
  const selectedPp = selectedPpId
    ? publicationPoints.find((pp) => pp.ppId === selectedPpId) ?? null
    : null;
  const objectsQuery = useQuery({
    queryKey: ["publication-point-objects", runId, selectedPp?.ppId ?? "", objectPage.cursor],
    queryFn: () => listObjectsForPublicationPoint(runId, selectedPp?.ppId ?? "", OBJECT_PAGE_SIZE, objectPage.cursor),
    enabled: Boolean(selectedPp?.ppId)
  });
  const objects = useMemo(() => objectsQuery.data?.data ?? [], [objectsQuery.data?.data]);
  const filteredObjects = useMemo(() => filterObjects(objects, objectFilter), [objectFilter, objects]);
  const repoTotal = reposQuery.data?.page?.nextCursor ? undefined : repoPage.previous.length * REPO_PAGE_SIZE + repos.length;
  const ppTotal = selectedRepo?.publicationPoints;
  const objectTotal = selectedPp?.objects;

  const error = latestRunQuery.error ?? reposQuery.error ?? ppsQuery.error ?? objectsQuery.error;

  return (
    <section className="page-stack repositories-page" aria-labelledby="repositories-heading">
      <div className="hero-dashboard compact-hero">
        <div>
          <p className="eyebrow">Repository browser · live query service</p>
          <h2 id="repositories-heading">Repository / publication point / object browser</h2>
          <p>Browse repo trees first, then expand publication points and object rows only on demand.</p>
        </div>
        <div className="hero-status">
          <Database size={20} />
          <div>
            <strong>{latestRunQuery.data?.data.runId ?? "Loading run"}</strong>
            <span>{repos.length ? `${repos.length} repositories loaded` : "waiting for query service"}</span>
          </div>
        </div>
      </div>

      {error ? (
        <div className="alert-banner" role="status">
          <strong>Repository API unavailable</strong>
          <span>{normalizeApiError(error).message}</span>
        </div>
      ) : null}

      <div className={`repo-browser-grid ${isRepoPanelCollapsed ? "repo-panel-collapsed" : ""} ${isPpPanelCollapsed ? "pp-panel-collapsed" : ""}`}>
        <section className="panel list-panel collapsible-list-panel repo-list-panel" aria-label="Repositories">
          <PanelTitle
            icon={<Database size={18} />}
            isCollapsed={isRepoPanelCollapsed}
            label="Repositories"
            meta={reposQuery.isFetching ? "loading" : rangeLabel(repoPage.previous.length + 1, REPO_PAGE_SIZE, repos.length, repoTotal, repoFilter, filteredRepos.length)}
            onToggleCollapse={() => onBrowserStateChange((state) => ({ ...state, isRepoPanelCollapsed: !state.isRepoPanelCollapsed }))}
            toggleLabel={isRepoPanelCollapsed ? "Expand repositories column" : "Collapse repositories column"}
          />
          {isRepoPanelCollapsed ? (
            <CollapsedPanelSummary icon={<Database size={20} />} label="Repositories" value={repoTotal ?? repos.length} />
          ) : (
            <>
          <CurrentPageFilter
            label="Filter repositories on current page"
            onChange={setRepoFilter}
            placeholder="Filter host or URI..."
            value={repoFilter}
          />
          <div className="stack-list">
            {filteredRepos.map((repo) => (
              <article className={`stack-row ${repo.repoId === selectedRepo?.repoId ? "active" : ""}`} key={repo.repoId}>
                <button
                  className="stack-row-select"
                  onClick={() => {
                    onBrowserStateChange((state) => ({
                      ...state,
                      selectedRepoId: repo.repoId,
                      selectedPpId: null,
                      ppFilter: "",
                      objectFilter: "",
                      ppPage: createEmptyPageState(),
                      objectPage: createEmptyPageState()
                    }));
                  }}
                  type="button"
                >
                  <span>
                    <strong>{repo.host}</strong>
                    <small>{repo.transport.toUpperCase()} · {repo.publicationPoints.toLocaleString()} publication points</small>
                  </span>
                  <em>{repo.objects.toLocaleString()}</em>
                  <ChevronRight size={16} />
                </button>
                <CopyableValue className="stack-row-copy" label="repository URI" value={repo.uri} />
              </article>
            ))}
          </div>
          <CursorPager
            isFetching={reposQuery.isFetching}
            pageNumber={repoPage.previous.length + 1}
            nextCursor={reposQuery.data?.page?.nextCursor ?? null}
            onNext={() => setRepoPage((page) => advancePage(page, reposQuery.data?.page?.nextCursor ?? null))}
            onPrevious={() => setRepoPage(previousPage)}
            previousCount={repoPage.previous.length}
            rangeLabel={rangeLabel(repoPage.previous.length + 1, REPO_PAGE_SIZE, repos.length, repoTotal, repoFilter, filteredRepos.length)}
          />
            </>
          )}
        </section>

        <section className="panel list-panel collapsible-list-panel pp-list-panel" aria-label="Publication points">
          <PanelTitle
            icon={<GitBranch size={18} />}
            isCollapsed={isPpPanelCollapsed}
            label="Publication Points"
            meta={ppsQuery.isFetching ? "loading" : selectedRepo ? rangeLabel(ppPage.previous.length + 1, PP_PAGE_SIZE, publicationPoints.length, ppTotal, ppFilter, filteredPublicationPoints.length) : "select repo"}
            onToggleCollapse={() => onBrowserStateChange((state) => ({ ...state, isPpPanelCollapsed: !state.isPpPanelCollapsed }))}
            toggleLabel={isPpPanelCollapsed ? "Expand publication points column" : "Collapse publication points column"}
          />
          {isPpPanelCollapsed ? (
            <CollapsedPanelSummary icon={<GitBranch size={20} />} label="Publication Points" value={ppTotal ?? publicationPoints.length} />
          ) : (
            <>
          {!selectedRepo ? (
            <div className="empty-state">Select a repository to load its publication points.</div>
          ) : null}
          <CurrentPageFilter
            disabled={!selectedRepo}
            label="Filter publication points on current page"
            onChange={setPpFilter}
            placeholder="Filter manifest, rsync, source..."
            value={ppFilter}
          />
          {ppsQuery.isFetching ? <LoadingLine /> : null}
          <div className="stack-list">
            {filteredPublicationPoints.map((pp) => (
              <article className={`stack-row ${pp.ppId === selectedPp?.ppId ? "active" : ""}`} key={pp.ppId}>
                <button
                  className="stack-row-select"
                  onClick={() => {
                    onBrowserStateChange((state) => ({
                      ...state,
                      selectedPpId: pp.ppId,
                      objectFilter: "",
                      objectPage: createEmptyPageState()
                    }));
                  }}
                  type="button"
                >
                  <span>
                    <strong>{shortName(pp.manifestRsyncUri ?? pp.publicationPointRsyncUri ?? pp.rrdpNotificationUri ?? pp.ppId)}</strong>
                    <small>{pp.repoSyncSource ?? pp.source ?? "unknown"} · {pp.ppId}</small>
                  </span>
                  <em>{pp.objects.toLocaleString()}</em>
                  <StatusPill state={pp.repoTerminalState ?? pp.source ?? "unknown"} />
                </button>
                <CopyableValue className="stack-row-copy" label="publication point URI" value={pp.publicationPointRsyncUri ?? pp.rsyncBaseUri ?? pp.rrdpNotificationUri ?? pp.ppId} />
                {pp.manifestRsyncUri ? <CopyableValue className="stack-row-copy secondary" label="manifest URI" value={pp.manifestRsyncUri} /> : null}
              </article>
            ))}
          </div>
          <CursorPager
            isFetching={ppsQuery.isFetching}
            nextCursor={ppsQuery.data?.page?.nextCursor ?? null}
            onNext={() => setPpPage((page) => advancePage(page, ppsQuery.data?.page?.nextCursor ?? null))}
            onPrevious={() => setPpPage(previousPage)}
            pageNumber={ppPage.previous.length + 1}
            previousCount={ppPage.previous.length}
            rangeLabel={selectedRepo ? rangeLabel(ppPage.previous.length + 1, PP_PAGE_SIZE, publicationPoints.length, ppTotal, ppFilter, filteredPublicationPoints.length) : undefined}
          />
            </>
          )}
        </section>

        <section className="panel table-panel objects-live-panel" aria-label="Objects for publication point">
          <PanelTitle
            icon={<FileCode2 size={18} />}
            label="Objects"
            meta={objectsQuery.isFetching ? "loading" : selectedPp ? rangeLabel(objectPage.previous.length + 1, OBJECT_PAGE_SIZE, objects.length, objectTotal, objectFilter, filteredObjects.length) : "select PP"}
          />
          {!selectedPp ? (
            <div className="empty-state">Select a publication point to load its objects.</div>
          ) : null}
          <CurrentPageFilter
            disabled={!selectedPp}
            label="Filter objects on current page"
            onChange={setObjectFilter}
            placeholder="Filter type, URI, hash, status..."
            value={objectFilter}
          />
          {objectsQuery.isFetching ? <LoadingLine /> : null}
          {selectedPp && !objectsQuery.isFetching ? (
            <ObjectTable objects={filteredObjects} onOpenObject={onOpenObject} />
          ) : null}
          <CursorPager
            isFetching={objectsQuery.isFetching}
            nextCursor={objectsQuery.data?.page?.nextCursor ?? null}
            onNext={() => setObjectPage((page) => advancePage(page, objectsQuery.data?.page?.nextCursor ?? null))}
            onPrevious={() => setObjectPage(previousPage)}
            pageNumber={objectPage.previous.length + 1}
            previousCount={objectPage.previous.length}
            rangeLabel={selectedPp ? rangeLabel(objectPage.previous.length + 1, OBJECT_PAGE_SIZE, objects.length, objectTotal, objectFilter, filteredObjects.length) : undefined}
          />
        </section>
      </div>
    </section>
  );
}

function PanelTitle({
  icon,
  isCollapsed,
  label,
  meta,
  onToggleCollapse,
  toggleLabel
}: {
  icon: ReactNode;
  isCollapsed?: boolean;
  label: string;
  meta: string;
  onToggleCollapse?: () => void;
  toggleLabel?: string;
}) {
  return (
    <div className="panel-heading compact-heading">
      <div>
        <p className="eyebrow">{label}</p>
        <h3>{label}</h3>
      </div>
      <div className="panel-heading-actions">
        <span className="panel-meta">
          {icon}
          {meta}
        </span>
        {onToggleCollapse ? (
          <button aria-expanded={!isCollapsed} aria-label={toggleLabel} className="panel-collapse-button" onClick={onToggleCollapse} title={toggleLabel} type="button">
            {isCollapsed ? <PanelRightClose aria-hidden="true" size={16} /> : <PanelLeftClose aria-hidden="true" size={16} />}
          </button>
        ) : null}
      </div>
    </div>
  );
}

function CollapsedPanelSummary({ icon, label, value }: { icon: ReactNode; label: string; value: number }) {
  return (
    <div className="collapsed-panel-summary">
      {icon}
      <strong>{label}</strong>
      <span>{value.toLocaleString()}</span>
    </div>
  );
}

function CurrentPageFilter({
  disabled,
  label,
  onChange,
  placeholder,
  value
}: {
  disabled?: boolean;
  label: string;
  onChange: (value: string) => void;
  placeholder: string;
  value: string;
}) {
  return (
    <label className="current-page-filter">
      <span>{label}</span>
      <input
        disabled={disabled}
        onChange={(event) => onChange(event.target.value)}
        placeholder={placeholder}
        value={value}
      />
    </label>
  );
}

function ObjectTable({ objects, onOpenObject }: { objects: ObjectInstanceRecord[]; onOpenObject?: (objectInstanceId: string) => void }) {
  if (objects.length === 0) {
    return <div className="empty-state">No objects returned for this publication point.</div>;
  }
  return (
    <table>
      <thead>
        <tr>
          <th>Type</th>
          <th>URI</th>
          <th>Hash</th>
          <th>Source</th>
          <th>Result</th>
          <th>Action</th>
        </tr>
      </thead>
      <tbody>
        {objects.map((object) => (
          <tr key={object.objectInstanceId}>
            <td><span className="object-type">{object.objectType}</span></td>
            <td className="uri-cell"><CopyableValue label="object URI" value={object.uri} /></td>
            <td><CopyableValue label="object SHA256" value={object.sha256} displayValue={shortHash(object.sha256, 12)} /></td>
            <td>{object.sourceSection}</td>
            <td><StatusPill state={object.rejected ? "rejected" : object.result} /></td>
            <td>
              <button className="table-link-button" onClick={() => onOpenObject?.(object.objectInstanceId)} type="button">
                Open
              </button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

function StatusPill({ state }: { state: string }) {
  const normalized = state.toLowerCase();
  const className = normalized.includes("fail") || normalized.includes("reject") || normalized.includes("error")
    ? "warning"
    : normalized.includes("cache") || normalized.includes("fallback")
      ? "fallback"
      : "healthy";
  return <span className={`status-badge ${className}`}>{state}</span>;
}

function LoadingLine() {
  return (
    <div className="loading-line">
      <Loader2 size={16} />
      Loading selected branch...
    </div>
  );
}

function shortName(uri: string) {
  return uri.split("/").filter(Boolean).at(-1) ?? uri;
}

function shortHash(value: string, prefixLength: number) {
  return `${value.slice(0, prefixLength)}...`;
}

function rangeLabel(pageNumber: number, pageSize: number, pageRowCount: number, total: number | undefined, filter: string, visibleRowCount = pageRowCount) {
  const start = pageRowCount === 0 ? 0 : (pageNumber - 1) * pageSize + 1;
  const end = pageRowCount === 0 ? 0 : start + pageRowCount - 1;
  const totalText = total === undefined ? "unknown" : total.toLocaleString();
  const filterText = filter.trim() ? ` · ${visibleRowCount.toLocaleString()}/${pageRowCount.toLocaleString()} matched on page` : "";
  return `${start.toLocaleString()}-${end.toLocaleString()}/${totalText} shown${filterText}`;
}

function matchesNeedle(values: Array<string | number | null | undefined>, needle: string) {
  const normalized = needle.trim().toLowerCase();
  if (!normalized) {
    return true;
  }
  return values.some((value) => String(value ?? "").toLowerCase().includes(normalized));
}

function filterRepositories(repos: RepositoryRecord[], filter: string) {
  return repos.filter((repo) => matchesNeedle([repo.host, repo.uri, repo.transport, repo.objects, repo.rejectedObjects], filter));
}

function filterPublicationPoints(publicationPoints: PublicationPointRecord[], filter: string) {
  return publicationPoints.filter((pp) => matchesNeedle([
    pp.ppId,
    pp.manifestRsyncUri,
    pp.publicationPointRsyncUri,
    pp.rsyncBaseUri,
    pp.rrdpNotificationUri,
    pp.source,
    pp.repoSyncSource,
    pp.repoTerminalState,
    pp.objects,
    pp.rejectedObjects
  ], filter));
}

function filterObjects(objects: ObjectInstanceRecord[], filter: string) {
  return objects.filter((object) => matchesNeedle([
    object.objectType,
    object.uri,
    object.sha256,
    object.sourceSection,
    object.result,
    object.rejectReason,
    object.rejected ? "rejected" : "accepted"
  ], filter));
}
