import type { PdfTextLine, ScientificBlock, ScientificBlockKind } from './types';

function median(values: number[]): number {
  if (!values.length) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const middle = Math.floor(sorted.length / 2);
  return sorted.length % 2 ? sorted[middle]! : (sorted[middle - 1]! + sorted[middle]!) / 2;
}

function normalize(text: string): string {
  return text.replace(/\s+/gu, ' ').trim();
}

function headingLike(text: string, fontSize: number, medianFont: number): boolean {
  if (!text || text.length > 180) return false;
  if (/^(abstract|references|bibliography|acknowledg(?:e)?ments?)$/iu.test(text)) return true;
  if (/^(?:\d+(?:\.\d+)*|[IVXLC]+)\.?\s+[\p{L}]/u.test(text)) return true;
  return medianFont > 0 && fontSize >= medianFont * 1.18 && text.split(/\s+/u).length <= 18;
}

function captionKind(text: string): ScientificBlockKind | undefined {
  if (/^(?:fig(?:ure)?\.?\s*)\d+[.:\s]/iu.test(text)) return 'figure-caption';
  if (/^table\s*\d+[.:\s]/iu.test(text)) return 'table-caption';
  return undefined;
}

function equationLike(text: string): boolean {
  const compact = text.replace(/\s+/gu, '');
  if (compact.length < 5) return false;
  const math = compact.match(/[=+−–*/^_{}()[\]∑∫√≤≥≈∞α-ωΑ-Ω]/gu)?.length ?? 0;
  return math >= 2 && math / compact.length >= 0.08;
}

function theoremLike(text: string): boolean {
  return /^(?:theorem|lemma|proposition|corollary|definition|remark)\b/iu.test(text);
}

function blockKind(text: string, inAbstract: boolean, inReferences: boolean): ScientificBlockKind {
  if (inReferences) return 'references';
  if (inAbstract) return 'abstract';
  return captionKind(text) ?? (theoremLike(text) ? 'theorem' : equationLike(text) ? 'equation-context' : 'paragraph');
}

/** Conservative line ordering with a simple two-column fallback. */
export function orderPdfLines(lines: readonly PdfTextLine[], pageWidth: number): PdfTextLine[] {
  const clean = lines.filter((line) => normalize(line.text));
  if (clean.length < 8 || pageWidth <= 0) return [...clean].sort((a, b) => b.y - a.y || a.x - b.x);
  const center = pageWidth / 2;
  const crossing = clean.filter((line) => line.x < center && line.x + line.width > center).length;
  const left = clean.filter((line) => line.x + line.width <= center * 1.04);
  const right = clean.filter((line) => line.x >= center * 0.96);
  if (left.length < 4 || right.length < 4 || crossing > clean.length * 0.2) {
    return [...clean].sort((a, b) => b.y - a.y || a.x - b.x);
  }
  const fullWidth = clean.filter((line) => !left.includes(line) && !right.includes(line));
  const topFull = fullWidth.sort((a, b) => b.y - a.y || a.x - b.x);
  return [
    ...topFull,
    ...left.sort((a, b) => b.y - a.y || a.x - b.x),
    ...right.sort((a, b) => b.y - a.y || a.x - b.x),
  ];
}

export function scientificBlocksFromPages(
  pages: readonly { pageNumber: number; width: number; lines: readonly PdfTextLine[] }[],
): ScientificBlock[] {
  const allFonts = pages.flatMap((page) => page.lines.map((line) => line.fontSize).filter((size) => size > 0));
  const medianFont = median(allFonts) || 10;
  const blocks: ScientificBlock[] = [];
  let section: string | undefined;
  let inAbstract = false;
  let inReferences = false;

  for (const page of pages) {
    const lines = orderPdfLines(page.lines, page.width);
    let paragraph: PdfTextLine[] = [];
    const flushParagraph = () => {
      if (!paragraph.length) return;
      const text = normalize(paragraph.map((line) => line.text).join(' '));
      paragraph = [];
      if (!text) return;
      const kind = blockKind(text, inAbstract, inReferences);
      blocks.push({
        id: `p${page.pageNumber}:b${blocks.length}`,
        kind,
        text,
        pageStart: page.pageNumber,
        pageEnd: page.pageNumber,
        ...(section ? { section } : {}),
      });
    };

    for (let index = 0; index < lines.length; index += 1) {
      const line = lines[index]!;
      const text = normalize(line.text);
      if (!text) continue;
      if (headingLike(text, line.fontSize, medianFont)) {
        flushParagraph();
        section = text;
        inAbstract = /^abstract$/iu.test(text);
        if (/^(?:references|bibliography)$/iu.test(text)) inReferences = true;
        else if (!inAbstract) inReferences = false;
        blocks.push({
          id: `p${page.pageNumber}:h${blocks.length}`,
          kind: 'heading',
          text,
          pageStart: page.pageNumber,
          pageEnd: page.pageNumber,
          heading: text,
          section: text,
        });
        continue;
      }
      const caption = captionKind(text);
      if (caption) {
        flushParagraph();
        blocks.push({
          id: `p${page.pageNumber}:c${blocks.length}`,
          kind: caption,
          text,
          pageStart: page.pageNumber,
          pageEnd: page.pageNumber,
          ...(section ? { section } : {}),
        });
        continue;
      }
      const previous = paragraph.at(-1);
      const verticalGap = previous ? Math.abs(previous.y - line.y) : 0;
      const expected = previous ? Math.max(previous.height, previous.fontSize, 8) : 0;
      if (previous && verticalGap > expected * 1.8) flushParagraph();
      paragraph.push(line);
    }
    flushParagraph();
  }
  return blocks;
}

/** Image/scanned PDFs must be reported explicitly instead of producing an empty index. */
export function isLikelyScannedPdf(
  pages: readonly { text: string }[],
): boolean {
  if (pages.length < 2) return false;
  const totalChars = pages.reduce((sum, page) => sum + page.text.replace(/\s+/gu, '').length, 0);
  const blankPages = pages.filter((page) => page.text.trim().length < 20).length;
  return totalChars < Math.max(100, pages.length * 35) ||
    (blankPages / pages.length >= 0.8 && totalChars < pages.length * 100);
}
