/**
 * CE-WP-0002-T09 — end-to-end test of the PRD slice-1 scenario.
 *
 * Steps verified (per the workplan):
 *   1. Open the app.
 *   2. Pick a fixture PDF.
 *   3. Programmatically inject a selection for the manifest's known-good
 *      quote (the actual drag-select interaction is verified manually —
 *      see ADR-0004 for the headless-Chromium limitation).
 *   4. Save an evidence item with a comment.
 *   5. The item appears in the sidebar.
 *   6. Reload the page (unmount + remount with the same localStorage).
 *   7. Click the evidence item.
 *   8. The viewer is asked to scroll to the correct annotation
 *      (the visual highlight render is not exercised here — see ADR-0004).
 *
 * The PDF.js viewer + ingest pipeline are mocked. Both have dedicated
 * coverage elsewhere (`src/source/pdf/ingest.test.ts`,
 * `tests/integration/anchor-source-roundtrip.test.ts`,
 * `src/anchor/pdf-selector-math.test.ts`). T09 owns the *wiring* of the
 * full UX flow; the PDF-rendering correctness lives in those layers.
 */

// @vitest-environment happy-dom

import { act, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import type { Selector } from "@shared/selector";
import type { PdfSelectionCapture } from "@anchor/index";
import manifest from "../../fixtures/pdfs/manifest.json" with { type: "json" };

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------

interface ViewerProps {
  pdfUrl: string;
  storedAnnotations: readonly { id: string; text: string; selectors: readonly Selector[] }[];
  scrollToAnnotationId?: string;
  onSelectionCaptured(capture: PdfSelectionCapture, selectors: Selector[]): void;
}

interface ViewerSnapshot {
  pdfUrl: string | null;
  storedAnnotationIds: string[];
  scrollToAnnotationId: string | null;
  onSelectionCaptured: ViewerProps["onSelectionCaptured"] | null;
}

const viewerSnapshot: ViewerSnapshot = {
  pdfUrl: null,
  storedAnnotationIds: [],
  scrollToAnnotationId: null,
  onSelectionCaptured: null,
};

vi.mock("@anchor/index", async (importOriginal) => {
  const original = await importOriginal<typeof import("@anchor/index")>();
  const MockPdfSpikeViewer = (props: ViewerProps) => {
    viewerSnapshot.pdfUrl = props.pdfUrl;
    viewerSnapshot.storedAnnotationIds = props.storedAnnotations.map((a) => a.id);
    viewerSnapshot.scrollToAnnotationId = props.scrollToAnnotationId ?? null;
    viewerSnapshot.onSelectionCaptured = props.onSelectionCaptured;
    return (
      <div
        data-testid="mock-pdf-viewer"
        data-pdf-url={props.pdfUrl}
        data-scroll-to={props.scrollToAnnotationId ?? ""}
        data-stored-count={String(props.storedAnnotations.length)}
      />
    );
  };
  return {
    ...original,
    PdfSpikeViewer: MockPdfSpikeViewer,
  };
});

// Mock ingestPdf so the click-to-load flow doesn't pull PDF.js into jsdom.
// The synthetic representation includes the manifest's known-good quote in
// its canonical text so createSelectors/resolveSelectors run for real.
const FIXTURE = manifest.fixtures.find((f) => f.id === "fristsetzung-bezifferung")!;
const SYNTHETIC_CANONICAL = [
  "Header boilerplate that comes before the quote.",
  FIXTURE.known_good_quote,
  "Trailing prose that comes after the quote.",
].join(" ");

// CE-WP-0005: pre-seed a session with the fixture doc; no ingestPdf mock needed.
import { seedSessionWithDoc, type SeedResult } from "./helpers/seed-session";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function resetViewerSnapshot() {
  viewerSnapshot.pdfUrl = null;
  viewerSnapshot.storedAnnotationIds = [];
  viewerSnapshot.scrollToAnnotationId = null;
  viewerSnapshot.onSelectionCaptured = null;
}

function syntheticCaptureFor(fixturePage: number, text: string): PdfSelectionCapture {
  return {
    kind: "pdf",
    text,
    page: fixturePage,
    rects: [{ x: 0.1, y: 0.2, width: 0.4, height: 0.04 }],
    boundingRect: { x: 0.1, y: 0.2, width: 0.4, height: 0.04 },
  };
}

async function loadApp() {
  // Late import so the vi.mock calls take effect before the module graph
  // pulls in @anchor / @source.
  const { App } = await import("@app/App");
  return render(<App />);
}

// ---------------------------------------------------------------------------
// Test
// ---------------------------------------------------------------------------

describe("App — PRD scenario steps 1-8 (CE-WP-0002-T09)", () => {
  let seeded: SeedResult;

  beforeEach(() => {
    resetViewerSnapshot();
    // Each test starts with empty localStorage.
    globalThis.localStorage?.clear();
    if (typeof window !== "undefined") {
      history.replaceState(null, "", window.location.pathname);
    }
    seeded = seedSessionWithDoc({
      sessionName: "Demo",
      documentTitle: FIXTURE.filename,
      canonicalText: SYNTHETIC_CANONICAL,
    });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("walks the full slice-1 scenario: load → select → save → reload → click → scroll", { timeout: 15000 }, async () => {
    const user = userEvent.setup();

    // Step 1: open the app. CE-WP-0005: a pre-seeded session boots
    // straight into the active layout (no empty state).
    const { unmount } = await loadApp();
    expect(screen.queryByTestId("empty-state")).toBeNull();
    expect(screen.getByTestId("session-menu-toggle").textContent).toContain("Demo");

    // Step 2: the fixture doc is pre-seeded into the active session, so
    // the viewer mounts automatically — no fixture-button click needed.
    await waitFor(() => {
      expect(viewerSnapshot.onSelectionCaptured).not.toBeNull();
    });

    // Step 3: programmatically inject a selection for the known-good quote.
    expect(viewerSnapshot.onSelectionCaptured).not.toBeNull();
    await act(async () => {
      viewerSnapshot.onSelectionCaptured!(
        syntheticCaptureFor(FIXTURE.known_good_quote_page, FIXTURE.known_good_quote),
        [{ type: "TextQuoteSelector", exact: FIXTURE.known_good_quote }],
      );
    });

    // The toolbar should appear with the quoted text preview.
    const toolbar = await screen.findByText(/New annotation/);
    expect(toolbar).toBeTruthy();

    // Step 4: add a comment and save.
    const textarea = screen.getByPlaceholderText(/Add a one-line comment/);
    await user.type(textarea, "Important deadline clause");
    await user.click(screen.getByRole("button", { name: /Save evidence/ }));

    // Step 5: the item appears in the sidebar. The commentary text is
    // unique to the right pane (the collection list never echoes it back).
    await screen.findByText(/Important deadline clause/);

    // The mock viewer should now know about the stored annotation.
    await waitFor(() => {
      expect(viewerSnapshot.storedAnnotationIds.length).toBe(1);
    });
    const savedAnnotationId = viewerSnapshot.storedAnnotationIds[0]!;

    // Per-session snapshot key from CE-WP-0005 — implementation detail
    // but worth asserting once at the integration layer.
    const stored = globalThis.localStorage?.getItem(
      `citation-evidence:session:${seeded.sessionId}:engine-snapshot:v1`,
    );
    expect(stored).toBeTruthy();

    // Step 6: reload — unmount and remount the App. The same localStorage is
    // still in place because we did not clear it.
    unmount();
    resetViewerSnapshot();
    await loadApp();

    // The viewer should re-mount automatically because the active document
    // was persisted.
    await waitFor(() => {
      expect(viewerSnapshot.onSelectionCaptured).not.toBeNull();
    });

    // The sidebar should show the restored item.
    const restoredItem = await screen.findByText(/Important deadline clause/);

    // The viewer must already know about the restored annotation.
    expect(viewerSnapshot.storedAnnotationIds).toContain(savedAnnotationId);

    // Step 7: click the evidence item in the sidebar. The commentary text
    // is rendered inside the sidebar's <button>; walking up to its closest
    // button ancestor and clicking it triggers the activate flow.
    const sidebarButton = restoredItem.closest("button");
    expect(sidebarButton).not.toBeNull();
    await user.click(sidebarButton!);

    // Step 8: the viewer was asked to scroll to the right annotation.
    await waitFor(() => {
      expect(viewerSnapshot.scrollToAnnotationId).toBe(savedAnnotationId);
    });
  });
});
