import { classifyParagraph, normalizeReadableText } from "../../domain/text.ts";

export interface OcrBlock {
  text: string;
  x: number;
  y: number;
  width: number;
  height: number;
  confidence: number;
}

export interface OrderedOcrBlocks extends Array<OcrBlock> {
  layout: "single" | "two-column";
}

export interface OcrPage {
  pageNumber: number;
  width: number;
  height: number;
  blocks: OcrBlock[];
}

export interface CleanedOcrBlock extends OcrBlock {
  included: boolean;
  warning?:
    | "repeated-header-footer"
    | "page-number"
    | "low-confidence"
    | "formula"
    | "table";
}

function middleCrossings(blocks: readonly OcrBlock[], pageWidth: number): number {
  const middle = pageWidth / 2;
  return blocks.filter((block) => block.x < middle && block.x + block.width > middle).length;
}

function detectLayout(blocks: readonly OcrBlock[], pageWidth: number) {
  const substantial = blocks.filter((block) => block.width > pageWidth * 0.08);
  const left = substantial.filter((block) => block.x + block.width <= pageWidth * 0.54);
  const right = substantial.filter((block) => block.x >= pageWidth * 0.46);
  const crosses = middleCrossings(substantial, pageWidth);
  return left.length >= 2 && right.length >= 2 && crosses <= substantial.length * 0.2
    ? "two-column" as const
    : "single" as const;
}

export function orderPageBlocks(
  blocks: readonly OcrBlock[],
  pageWidth: number,
): OrderedOcrBlocks {
  const layout = detectLayout(blocks, pageWidth);
  const ordered = [...blocks].sort((left, right) => {
    if (layout === "two-column") {
      const leftColumn = left.x + left.width / 2 < pageWidth / 2 ? 0 : 1;
      const rightColumn = right.x + right.width / 2 < pageWidth / 2 ? 0 : 1;
      if (leftColumn !== rightColumn) return leftColumn - rightColumn;
    }
    return left.y - right.y || left.x - right.x;
  }) as OrderedOcrBlocks;
  ordered.layout = layout;
  return ordered;
}

function normalizedRepeatKey(value: string): string {
  return normalizeReadableText(value).toLocaleLowerCase().replace(/\d+/g, "#");
}

function repeatedEdgeText(pages: readonly OcrPage[]): Set<string> {
  const counts = new Map<string, Set<number>>();
  for (const page of pages) {
    for (const block of page.blocks) {
      const atEdge = block.y <= page.height * 0.1 || block.y + block.height >= page.height * 0.9;
      const key = normalizedRepeatKey(block.text);
      if (!atEdge || key.length < 3 || key.length > 100) continue;
      const pageNumbers = counts.get(key) ?? new Set<number>();
      pageNumbers.add(page.pageNumber);
      counts.set(key, pageNumbers);
    }
  }
  const threshold = Math.max(2, Math.ceil(pages.length * 0.6));
  return new Set(
    [...counts.entries()]
      .filter(([, pageNumbers]) => pageNumbers.size >= threshold)
      .map(([key]) => key),
  );
}

function warningFor(
  block: OcrBlock,
  page: OcrPage,
  repeated: ReadonlySet<string>,
): CleanedOcrBlock["warning"] {
  const text = normalizeReadableText(block.text);
  if (repeated.has(normalizedRepeatKey(text))) return "repeated-header-footer";
  const atBottom = block.y + block.height >= page.height * 0.9;
  if (atBottom && /^[-–—]?\s*\d{1,4}\s*[-–—]?$/.test(text)) return "page-number";
  const kind = classifyParagraph(block.text);
  if (kind === "formula") return "formula";
  if (kind === "table") return "table";
  if (block.confidence < 60) return "low-confidence";
  return undefined;
}

export function cleanOcrPages(pages: readonly OcrPage[]) {
  const repeated = repeatedEdgeText(pages);
  return pages.map((page) => ({
    ...page,
    blocks: orderPageBlocks(page.blocks, page.width).map((block) => {
      const warning = warningFor(block, page, repeated);
      return {
        ...block,
        text: normalizeReadableText(block.text),
        included: !warning,
        warning,
      } satisfies CleanedOcrBlock;
    }),
  }));
}
