// Pure citation extraction over lecture segments. Regex-only; no network.
// Detects DOIs, arXiv ids, ISBN-10/13, and "Author (Year)" mentions.

import type { Segment } from '../types';
import type { Citation } from './types';

/** Detect and normalize citations across all segments. Deduped by value. */
export function detectCitations(segments: Segment[]): Citation[] {
  if (!segments.length) return [];

  const out: Citation[] = [];
  const seen = new Set<string>();

  const push = (c: Citation) => {
    const key = `${c.kind}:${c.value.toLowerCase()}`;
    if (seen.has(key)) return;
    seen.add(key);
    out.push(c);
  };

  for (const seg of segments) {
    const text = seg.text ?? '';
    if (!text) continue;

    for (const c of detectDois(text)) push(tag(c, seg));
    for (const c of detectArxiv(text)) push(tag(c, seg));
    for (const c of detectIsbn(text)) push(tag(c, seg));
    for (const c of detectAuthorYear(text)) push(tag(c, seg));
  }

  return out;
}

function tag(
  c: Omit<Citation, 'sourceSegmentId' | 'start'>,
  seg: Segment,
): Citation {
  return { ...c, sourceSegmentId: seg.id, start: seg.start };
}

// ---------------------------------------------------------------------------
// DOI
// ---------------------------------------------------------------------------

function detectDois(text: string): Omit<Citation, 'sourceSegmentId' | 'start'>[] {
  const out: Omit<Citation, 'sourceSegmentId' | 'start'>[] = [];
  // DOIs start with 10.<registrant>/<suffix>. Allow an optional doi: / URL prefix.
  const re = /\b(?:doi:\s*|https?:\/\/(?:dx\.)?doi\.org\/)?(10\.\d{4,9}\/[^\s"<>]+)/gi;
  let m: RegExpExecArray | null;
  while ((m = re.exec(text))) {
    // Trim common trailing punctuation that isn't part of the DOI.
    const value = m[1]!.replace(/[.,;)\]]+$/, '');
    out.push({ kind: 'doi', value, raw: m[0] });
  }
  return out;
}

// ---------------------------------------------------------------------------
// arXiv
// ---------------------------------------------------------------------------

function detectArxiv(text: string): Omit<Citation, 'sourceSegmentId' | 'start'>[] {
  const out: Omit<Citation, 'sourceSegmentId' | 'start'>[] = [];

  // Explicit "arXiv:NNNN.NNNNN" (with optional version) — most reliable.
  const reExplicit = /\barxiv:\s*(\d{4}\.\d{4,5}(?:v\d+)?)\b/gi;
  let m: RegExpExecArray | null;
  while ((m = reExplicit.exec(text))) {
    out.push({ kind: 'arxiv', value: m[1]!, raw: m[0] });
  }

  // Bare "NNNN.NNNNN" — only when not already captured as part of an arXiv: hit.
  const reBare = /(?<![\w.])(\d{4}\.\d{4,5}(?:v\d+)?)(?![\w.])/g;
  while ((m = reBare.exec(text))) {
    const value = m[1]!;
    // Skip if this position is the tail of an explicit arXiv match.
    const preceding = text.slice(Math.max(0, m.index - 8), m.index).toLowerCase();
    if (preceding.includes('arxiv')) continue;
    out.push({ kind: 'arxiv', value, raw: m[0] });
  }

  return out;
}

// ---------------------------------------------------------------------------
// ISBN
// ---------------------------------------------------------------------------

function detectIsbn(text: string): Omit<Citation, 'sourceSegmentId' | 'start'>[] {
  const out: Omit<Citation, 'sourceSegmentId' | 'start'>[] = [];
  // ISBN-13 (978/979 prefix) and ISBN-10, hyphens/spaces allowed internally.
  const re =
    /\bISBN(?:-1[03])?:?\s*((?:97[89][-\s]?)?(?:\d[-\s]?){9}[\dXx])\b/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(text))) {
    const value = m[1]!.replace(/[-\s]/g, '').toUpperCase();
    if (value.length === 10 || value.length === 13) {
      out.push({ kind: 'isbn', value, raw: m[0] });
    }
  }
  return out;
}

// ---------------------------------------------------------------------------
// Author-year
// ---------------------------------------------------------------------------

function detectAuthorYear(
  text: string,
): Omit<Citation, 'sourceSegmentId' | 'start'>[] {
  const out: Omit<Citation, 'sourceSegmentId' | 'start'>[] = [];

  // "Smith (2020)", "Smith and Jones (2019)", "Smith et al., 2021",
  // "Smith et al. (2021)". Author = Capitalized word(s).
  const re =
    /\b([A-Z][a-z]+(?:[-\s][A-Z][a-z]+)*(?:\s+(?:and|&)\s+[A-Z][a-z]+)?(?:\s+et al\.?)?)\s*[,(]?\s*\(?(\b(?:18|19|20)\d{2})\)?/g;
  let m: RegExpExecArray | null;
  while ((m = re.exec(text))) {
    const author = stripLeadingStopword(m[1]!.trim().replace(/\s+/g, ' '));
    const year = m[2]!;
    // Require either a parenthesized year or an "et al"/"and" multi-author cue
    // to avoid matching plain prose like "World War 2 1945".
    const cue = m[0];
    const looksLikeCitation =
      /\(\s*(?:18|19|20)\d{2}\s*\)/.test(cue) ||
      /et al/i.test(cue) ||
      /\band\b|&/.test(cue);
    if (!looksLikeCitation) continue;
    const value = `${author} (${year})`;
    out.push({ kind: 'author-year', value, raw: m[0].trim() });
  }
  return out;
}

// Common capitalized sentence-starters that can be mistaken for a surname when
// they immediately precede the real author (e.g. "As Vaswani (2017)").
const LEADING_STOPWORDS = new Set([
  'As',
  'In',
  'By',
  'See',
  'Per',
  'From',
  'After',
  'Before',
  'And',
  'The',
  'This',
  'That',
  'These',
  'Those',
  'Both',
  'When',
  'While',
  'Since',
]);

function stripLeadingStopword(author: string): string {
  const tokens = author.split(' ');
  // Only strip when something real remains and the next token is itself a name.
  if (tokens.length > 1 && tokens[0] && LEADING_STOPWORDS.has(tokens[0])) {
    return tokens.slice(1).join(' ');
  }
  return author;
}
