/**
 * Throwaway PDF viewer adapter spike (CE-WP-0002-T02).
 *
 * Purpose: prove that `react-pdf-highlighter-plus` can implement the §5
 * `DocumentViewerAdapter` contract end-to-end (select → save selectors →
 * reload → resolve → scroll → render highlight) without leaking PDF.js
 * types into `src/shared/` or `src/engine/`.
 *
 * This module is the only place in the codebase that imports
 * `react-pdf-highlighter-plus`. The exported React component is consumed
 * by `src/app/SpikeApp.tsx`.
 *
 * Replace before production. T03 (source ingest) + T04 (anchor resolution)
 * will build the real PDFViewerAdapter on top of this lessons-learned.
 */

import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import {
  PdfHighlighter,
  PdfLoader,
  TextHighlight,
  MonitoredHighlightContainer,
  useHighlightContainerContext,
  type Highlight,
  type PdfHighlighterUtils,
  type PdfSelection,
  type ScaledPosition,
} from "react-pdf-highlighter-plus";
import "react-pdf-highlighter-plus/style/style.css";
import "react-pdf-highlighter-plus/style/pdf_viewer.css";
import "./highlight-styles.css";
import "./debug-textlayer.css";

import type { NormalizedRect, Selector } from "@shared/selector";
import type { AnchorResolution, PdfSelectionCapture, ResolvedAnchorTarget } from "./types";
import { findPdfRectSelector, selectorsFromPdfCapture, unionRect } from "./pdf-selector-math";

export { selectorsFromPdfCapture };

/**
 * Inverse of `selectorsFromPdfCapture`: build a viewer-renderable
 * `Highlight` from stored selectors. The spike's reload path leans on
 * `PdfRectSelector` since it carries page + page-relative rects directly.
 * T04 will own the production resolver and add the text-only paths.
 */
function highlightFromSelectors(
  id: string,
  text: string,
  selectors: readonly Selector[],
): Highlight | null {
  const rectSel = findPdfRectSelector(selectors);
  if (!rectSel) return null;
  const boundingRect = unionRect(rectSel.rects);
  if (!boundingRect) return null;
  const scaledRects = rectSel.rects.map((r) => toScaled(r, rectSel.page));
  return {
    id,
    type: "text",
    content: { text },
    position: {
      boundingRect: toScaled(boundingRect, rectSel.page),
      rects: scaledRects,
    } satisfies ScaledPosition,
  };
}

/**
 * Convert the adapter's `NormalizedRect` (page-relative 0..1) to the
 * `Scaled` shape react-pdf-highlighter-plus expects (also normalized 0..1
 * via width/height). We use a unit page-space of 1×1 — the library
 * computes pixel coords from `pageNumber` and the renderer's actual page
 * dimensions.
 */
function toScaled(r: NormalizedRect, page: number) {
  return {
    x1: r.x,
    y1: r.y,
    x2: r.x + r.width,
    y2: r.y + r.height,
    width: 1,
    height: 1,
    pageNumber: page,
  };
}

/** PdfSelection → our domain-neutral `PdfSelectionCapture`. */
function captureFromPdfSelection(sel: PdfSelection): PdfSelectionCapture {
  const page = sel.position.boundingRect.pageNumber;
  const rects = sel.position.rects.map<NormalizedRect>((r) => ({
    x: r.x1 / r.width,
    y: r.y1 / r.height,
    width: (r.x2 - r.x1) / r.width,
    height: (r.y2 - r.y1) / r.height,
  }));
  const br = sel.position.boundingRect;
  const boundingRect: NormalizedRect = {
    x: br.x1 / br.width,
    y: br.y1 / br.height,
    width: (br.x2 - br.x1) / br.width,
    height: (br.y2 - br.y1) / br.height,
  };
  return {
    kind: "pdf",
    text: sel.content.text ?? "",
    page,
    rects,
    boundingRect,
  };
}

/**
 * Trivial container that renders every stored highlight as a TextHighlight.
 * For the spike, no editing tooling — just visual proof of "did the saved
 * coordinates land on the right passage on the right page after reload?"
 */
interface HighlightContainerProps {
  readonly activeAnnotationId: string | null | undefined;
  readonly onHighlightClicked: ((annotationId: string) => void) | undefined;
}

function makeSpikeHighlightContainer(props: HighlightContainerProps) {
  return function SpikeHighlightContainer(): ReactNode {
    const { highlight, isScrolledTo } = useHighlightContainerContext();
    const isActive = props.activeAnnotationId === highlight.id;
    return (
      <div
        data-highlight-id={highlight.id}
        data-ce-active={isActive ? "true" : "false"}
        style={{ display: "contents" }}
        onClickCapture={(e) => {
          // Click-capture so the click registers before the library's
          // built-in selection-clearing logic eats it. Stop propagation
          // so the highlight click doesn't also start a new selection.
          e.stopPropagation();
          props.onHighlightClicked?.(highlight.id);
        }}
      >
        <MonitoredHighlightContainer>
          <TextHighlight highlight={highlight} isScrolledTo={isScrolledTo} />
        </MonitoredHighlightContainer>
      </div>
    );
  };
}

/**
 * Resolve the rendered DOM rect for a highlight by data attribute, or
 * `null` if the highlight isn't currently rendered (e.g. its page hasn't
 * scrolled into view). Used by `app/forms/HighlightRectBridge` to feed
 * the rect registry as kind="highlight".
 *
 * `display: contents` on the wrapper means it has no box of its own; we
 * union the rects of its children. For TextHighlight that's typically
 * one rect per line.
 */
export function getHighlightClientRects(annotationId: string): DOMRect | null {
  if (typeof document === "undefined") return null;
  const wrapper = document.querySelector(`[data-highlight-id="${CSS.escape(annotationId)}"]`);
  if (!wrapper) return null;
  const rects = wrapper.getClientRects();
  if (rects.length === 0) return null;
  let left = Infinity;
  let top = Infinity;
  let right = -Infinity;
  let bottom = -Infinity;
  for (const r of Array.from(rects)) {
    left = Math.min(left, r.left);
    top = Math.min(top, r.top);
    right = Math.max(right, r.right);
    bottom = Math.max(bottom, r.bottom);
  }
  if (!isFinite(left)) return null;
  return new DOMRect(left, top, right - left, bottom - top);
}

export interface PdfSpikeViewerProps {
  /** URL of the PDF to load (served by Vite dev server). */
  readonly pdfUrl: string;
  /** Previously-saved selector sets to restore on mount. */
  readonly storedAnnotations: readonly StoredAnnotation[];
  /** Called when the user produces a new selection. */
  onSelectionCaptured(capture: PdfSelectionCapture, selectors: Selector[]): void;
  /** Annotation id to scroll to and highlight on mount, if any. */
  readonly scrollToAnnotationId?: string;
  /**
   * Annotation id currently focused. The matching highlight gets a
   * thicker border (see highlight-styles.css). `null`/undefined means
   * "no active highlight".
   */
  readonly activeAnnotationId?: string | null;
  /**
   * Called when the user clicks an existing highlight in the page.
   * The receiver typically activates the matching evidence item.
   */
  onHighlightClicked?(annotationId: string): void;
  /**
   * When true, paint the PDF text-layer spans in light grey so it's
   * obvious which glyphs have a selectable text overlay and which are
   * image-only. Also logs every onSelection event to the console.
   */
  readonly debugTextLayer?: boolean;
}

export interface StoredAnnotation {
  readonly id: string;
  readonly text: string;
  readonly selectors: readonly Selector[];
}

/**
 * The spike's React component. Renders a PDF and:
 *  - emits `onSelectionCaptured(capture, selectors)` on every fresh selection
 *  - reconstructs and renders `storedAnnotations` immediately on load
 *  - scrolls to `scrollToAnnotationId` if its highlight can be reconstructed
 */
export function PdfSpikeViewer(props: PdfSpikeViewerProps) {
  const {
    pdfUrl,
    storedAnnotations,
    onSelectionCaptured,
    scrollToAnnotationId,
    activeAnnotationId,
    onHighlightClicked,
    debugTextLayer,
  } = props;
  const HighlightContainer = useMemo(
    () => makeSpikeHighlightContainer({ activeAnnotationId, onHighlightClicked }),
    [activeAnnotationId, onHighlightClicked],
  );
  const utilsRef = useRef<PdfHighlighterUtils | null>(null);
  const [didScroll, setDidScroll] = useState<string | null>(null);

  const highlights = useMemo<Highlight[]>(() => {
    const out: Highlight[] = [];
    const skipped: { id: string; reason: string }[] = [];
    for (const a of storedAnnotations) {
      const h = highlightFromSelectors(a.id, a.text, a.selectors);
      if (h) out.push(h);
      else skipped.push({ id: a.id, reason: "no PdfRectSelector / empty boundingRect" });
    }
    if (debugTextLayer) {
      console.log("[ce] viewer highlights", {
        in: storedAnnotations.length,
        rendered: out.length,
        rendered_detail: out.map((h) => ({
          id: h.id,
          page: h.position.boundingRect.pageNumber,
          bounding: h.position.boundingRect,
          rectCount: h.position.rects.length,
        })),
        skipped,
      });
    }
    return out;
  }, [storedAnnotations, debugTextLayer]);

  useEffect(() => {
    if (!scrollToAnnotationId || didScroll === scrollToAnnotationId) return;
    const utils = utilsRef.current;
    const target = highlights.find((h) => h.id === scrollToAnnotationId);
    if (debugTextLayer) {
      console.log("[ce] scrollToAnnotation requested", {
        id: scrollToAnnotationId,
        utilsAvailable: !!utils,
        targetFound: !!target,
        knownIds: highlights.map((h) => h.id),
      });
    }
    if (!utils || !target) return;
    utils.scrollToHighlight(target);
    setDidScroll(scrollToAnnotationId);
  }, [scrollToAnnotationId, highlights, didScroll, debugTextLayer]);

  return (
    <div
      className={debugTextLayer ? "ce-debug-textlayer" : undefined}
      style={{ height: "100%" }}
    >
      <PdfLoader document={pdfUrl}>
        {(pdfDocument) => (
          <PdfHighlighter
            pdfDocument={pdfDocument}
            highlights={highlights}
            utilsRef={(u) => {
              utilsRef.current = u;
            }}
            onSelection={(selection) => {
              const capture = captureFromPdfSelection(selection);
              const selectors = selectorsFromPdfCapture(capture);
              if (debugTextLayer) {
                console.log("[ce] onSelection", {
                  text: capture.text,
                  page: capture.page,
                  rects: capture.rects,
                  selectorTypes: selectors.map((s) => s.type),
                  raw: selection,
                });
              }
              onSelectionCaptured(capture, selectors);
            }}
          >
            <HighlightContainer />
          </PdfHighlighter>
        )}
      </PdfLoader>
    </div>
  );
}

// Re-export the §5 contract surface so callers see anchor as one entry point.
export type { AnchorResolution, ResolvedAnchorTarget, PdfSelectionCapture };
