/**
 * Shared types for the mupdf native-editing engine.
 *
 * Coordinate spaces (important):
 *  - mupdf "device space": TOP-LEFT origin, y-DOWN, points. For an unrotated page
 *    this is identical to YeetPDF's model/overlay space (unrotated page points,
 *    top-left, y-down) — device (0,0) maps to the media-box's top-left corner.
 *    All `Rect4`/point geometry the worker reports is in this space.
 *  - PDF content-stream space: BOTTOM-LEFT origin, y-UP. The worker converts to
 *    this internally when writing content (redraw text/images).
 *
 * This module has NO runtime imports so it can be shared by the worker bundle,
 * the main-thread client, and the editor overlay without pulling anything in.
 */

import type { FontVariant } from './fonts'; // type-only — erased at build, no runtime coupling

/** [x0, y0, x1, y1] in mupdf device space (top-left, y-down, points). */
export type Rect4 = [number, number, number, number];
/** [a, b, c, d, e, f] affine matrix. */
export type Mat6 = [number, number, number, number, number, number];

/** An editable run of text with one consistent font/size on a single line. */
export interface TextSpanInfo {
  index: number; // stable index within the page (discovery order)
  text: string;
  bbox: Rect4; // tight bounding box in device space
  origin: [number, number]; // baseline origin (x,y) of the first glyph, device space
  size: number; // font size in points
  fontName: string; // raw mupdf font name (may carry a subset prefix like "ABCDEE+")
  serif: boolean;
  mono: boolean;
  bold: boolean;
  italic: boolean;
  color: string; // hex color of the first glyph, e.g. "#12141b"
  dir: [number, number]; // writing direction unit vector (usually [1,0])
  /** True when the source glyphs use PDF text render mode 3 ("invisible") —
   *  the hidden OCR layer scanned PDFs place over a page image. There is
   *  nothing visible for a redaction to remove, so editing this span needs an
   *  opaque cover behind the new text (see TextEditParams.invisible). */
  invisible: boolean;
}

/** A raster image drawn on the page (an image XObject invocation). */
export interface ImageObjInfo {
  index: number; // stable index within the page (discovery order)
  bbox: Rect4; // device-space bounding box of the placed image
  matrix: Mat6; // the placement matrix (device space)
  width: number; // source image pixel width
  height: number; // source image pixel height
}

/** Result of querying a page's editable content. */
export interface PageContent {
  pageIndex: number;
  mediaBox: Rect4; // PDF user-space media box [x0,y0,x1,y1]
  width: number; // unrotated page width (points)
  height: number; // unrotated page height (points)
  rotation: number; // page /Rotate (0/90/180/270)
  spans: TextSpanInfo[];
  images: ImageObjInfo[];
}

/** A single rectangle to redact on a given page (device space). */
export interface RedactMark {
  pageIndex: number;
  rect: Rect4;
}

/** How images touched by a redaction are handled. */
export type RedactImageMode = 'remove' | 'pixels' | 'none';

/** Parameters for replacing the text of an existing span. */
export interface TextEditParams {
  pageIndex: number;
  spanIndex: number;
  bbox: Rect4; // the span box to clear (device space)
  origin: [number, number]; // baseline origin to draw at (device space)
  text: string; // new text
  size: number; // font size (points)
  color: string; // hex
  serif: boolean;
  mono: boolean;
  bold: boolean;
  italic: boolean;
  /** Resolved bundled-font id to embed (from resolveDocFont / inline picker).
   *  Absent or a core family → draw with the base-14 substitute. */
  fontFamily?: string;
  /** Which weight/style variant of `fontFamily` to embed. */
  variant?: FontVariant;
  /** Draw an underline beneath the text. */
  underline?: boolean;
  /** True when the user left the family/bold/italic controls at the span's
   *  original values. Lets the worker try reusing the span's own source font
   *  (byte-identical glyphs) instead of a metric-matched substitute, as long as
   *  every character in `text` already has a glyph in it — only a genuinely new
   *  character falls back to `fontFamily`/base-14 substitution. */
  reuseOriginalFont?: boolean;
  /** True when the span being replaced was invisible text (see TextSpanInfo.invisible)
   *  — a scanned page's OCR layer. The old glyphs were never visible, so the
   *  worker paints an opaque cover behind the new (visible) text instead of
   *  relying on the redaction step to have cleared anything on-screen. */
  invisible?: boolean;
}

/** Parameters for re-placing an existing image with a new transform. */
export interface ImageEditParams {
  pageIndex: number;
  imageIndex: number;
  oldBbox: Rect4; // the original image box to clear (device space)
  /** New placement box in device space (top-left, y-down). */
  box: { x: number; y: number; w: number; h: number };
  rotation: number; // clockwise degrees about the box center (screen convention)
  flipH: boolean;
  flipV: boolean;
}

/* ---------- worker message protocol ---------- */

export type MupdfOp =
  | { op: 'query-page'; pageIndex: number }
  | { op: 'redact'; marks: RedactMark[]; imageMode: RedactImageMode }
  | { op: 'edit-text'; params: TextEditParams }
  | { op: 'edit-image'; params: ImageEditParams }
  | { op: 'delete-image'; pageIndex: number; imageIndex: number; bbox: Rect4 }
  // Flatten native-edit history to clean, export-ready bytes (garbage-collect +
  // single xref), memory-light — used by the exporter's fast path to avoid a
  // pdf-lib rebuild of a large document.
  | { op: 'compact' };

export interface MupdfRequest {
  id: number;
  /** Present only when the worker's cached document is stale (bytes changed). */
  bytes?: Uint8Array;
  /** Monotonic generation of the bytes the worker should be operating on. */
  gen: number;
  payload: MupdfOp;
}

export interface MupdfResponse {
  id: number;
  ok: boolean;
  error?: string;
  /** Query result (for op 'query-page'). */
  page?: PageContent;
  /** New PDF bytes produced by an edit op. */
  output?: Uint8Array;
}
