import type { CitationImportFormat, CitationLibrarySource } from "@skola/shared";

export interface ParsedCitation {
  type: string;
  title: string;
  authors: string[];
  journal: string;
  volume: string;
  issue: string;
  pages: string;
  date: string;
  year: number;
  doi: string;
  abstract: string;
  isbn: string;
  publisher: string;
}

function currentYear(): number {
  return new Date().getUTCFullYear();
}

function parseYearFromDate(date: string): number {
  const m = date.match(/\b(\d{4})\b/);
  return m ? Number(m[1]) : 0;
}

// ---------------------------------------------------------------------------
// RIS parser
// ---------------------------------------------------------------------------

export function parseRis(content: string): ParsedCitation {
  let type = "Reference";
  let title = "";
  let journal = "";
  let volume = "";
  let issue = "";
  let startPage = "";
  let endPage = "";
  let date = "";
  let year = 0;
  let doi = "";
  let abstract = "";
  let isbn = "";
  let publisher = "";
  const authors: string[] = [];

  for (const rawLine of content.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line) continue;
    // RIS format: "TY  - value" (two-letter tag, two spaces, dash, space, value)
    const match = line.match(/^([A-Z][A-Z0-9])\s{2}-\s?(.*)$/);
    if (!match) continue;
    const [, tag, rawValue] = match;
    const value = rawValue.trim();

    switch (tag) {
      case "TY": type = value || type; break;
      case "AU": if (value) authors.push(value); break;
      case "A1": if (value) authors.push(value); break;
      case "TI": title = value || title; break;
      case "T1": if (!title) title = value; break;
      case "JO": journal = value || journal; break;
      case "JF": if (!journal) journal = value; break;
      case "T2": if (!journal) journal = value; break;
      case "VL": volume = value || volume; break;
      case "IS": issue = value || issue; break;
      case "SP": startPage = value || startPage; break;
      case "EP": endPage = value || endPage; break;
      case "PY": year = Number(value) || year; break;
      case "Y1": if (!year) year = Number(value) || 0; break;
      case "DA": date = value || date; break;
      case "DO": doi = value || doi; break;
      case "AB": abstract = value || abstract; break;
      case "N2": if (!abstract) abstract = value; break;
      case "SN": isbn = value || isbn; break;
      case "PB": publisher = value || publisher; break;
      case "ER": break;
    }
  }

  if (!title) throw new Error("missing title");

  const pages = startPage && endPage ? `${startPage}-${endPage}` : startPage || endPage;
  if (!year && date) year = parseYearFromDate(date);
  if (!year) year = currentYear();

  return { type, title, authors, journal, volume, issue, pages, date, year, doi, abstract, isbn, publisher };
}

// ---------------------------------------------------------------------------
// EndNote XML parser (regex-based, no DOM)
// ---------------------------------------------------------------------------

function extractXmlTag(xml: string, tag: string): string {
  const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i");
  const m = xml.match(re);
  if (!m) return "";
  const raw = m[1]
    .replace(/<[^>]+>/g, "")
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&apos;/g, "'")
    .trim();
  return raw;
}

function extractAllXmlTags(xml: string, tag: string): string[] {
  const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
  const results: string[] = [];
  let m: RegExpExecArray | null;
  while ((m = re.exec(xml)) !== null) {
    const text = m[1]
      .replace(/<[^>]+>/g, "")
      .replace(/&amp;/g, "&")
      .replace(/&lt;/g, "<")
      .replace(/&gt;/g, ">")
      .trim();
    if (text) results.push(text);
  }
  return results;
}

export function parseEndnoteXml(content: string): ParsedCitation {
  // Extract first <record> block
  const recordMatch = content.match(/<record>([\s\S]*?)<\/record>/i);
  if (!recordMatch) throw new Error("The XML file contains no <record> element.");
  const record = recordMatch[1];

  // Type from ref-type attribute
  const refTypeMatch = record.match(/<ref-type[^>]+name="([^"]+)"/i);
  const type = refTypeMatch ? refTypeMatch[1] : "Reference";

  // Title
  const titlesMatch = record.match(/<titles>([\s\S]*?)<\/titles>/i);
  const titlesBlock = titlesMatch ? titlesMatch[1] : "";
  const title = extractXmlTag(titlesBlock, "title");
  if (!title) throw new Error("missing title");

  const journal = extractXmlTag(titlesBlock, "secondary-title");

  // Authors
  const contribMatch = record.match(/<contributors>([\s\S]*?)<\/contributors>/i);
  const authorsBlock = contribMatch ? contribMatch[1] : "";
  const authors = extractAllXmlTags(authorsBlock, "author");

  const volume = extractXmlTag(record, "volume");
  const issue = extractXmlTag(record, "number");
  const pages = extractXmlTag(record, "pages");

  // Dates
  const datesMatch = record.match(/<dates>([\s\S]*?)<\/dates>/i);
  const datesBlock = datesMatch ? datesMatch[1] : "";
  const yearStr = extractXmlTag(datesBlock, "year");
  const year = Number(yearStr) || currentYear();

  const doi = extractXmlTag(record, "electronic-resource-num");
  const abstract = extractXmlTag(record, "abstract");
  const isbn = extractXmlTag(record, "isbn");
  const publisher = extractXmlTag(record, "publisher");

  return {
    type, title, authors, journal, volume, issue, pages,
    date: yearStr || "", year, doi, abstract, isbn, publisher
  };
}

// ---------------------------------------------------------------------------
// ENW parser
// ---------------------------------------------------------------------------

export function parseEnw(content: string): ParsedCitation {
  let type = "Reference";
  let title = "";
  let journal = "";
  let volume = "";
  let issue = "";
  let pages = "";
  let date = "";
  let doi = "";
  let abstract = "";
  let isbn = "";
  const authors: string[] = [];

  for (const rawLine of content.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line) continue;
    const match = line.match(/^%([0-9A-Z@])\s+(.*)$/);
    if (!match) continue;
    const [, code, rawValue] = match;
    const value = rawValue.trim();

    switch (code) {
      case "0": type = value || type; break;
      case "T": title = value || title; break;
      case "A": if (value) authors.push(value); break;
      case "J": journal = value || journal; break;
      case "V": volume = value || volume; break;
      case "N": issue = value || issue; break;
      case "P": pages = value || pages; break;
      case "D": date = value || date; break;
      case "U": doi = value || doi; break;
      case "R": if (!doi) doi = value; break;
      case "X": abstract = value || abstract; break;
      case "@": isbn = value || isbn; break;
    }
  }

  if (!title) throw new Error("missing title");

  const year = parseYearFromDate(date) || currentYear();

  return { type, title, authors, journal, volume, issue, pages, date, year, doi, abstract, isbn, publisher: "" };
}

// ---------------------------------------------------------------------------
// Unified dispatcher
// ---------------------------------------------------------------------------

function buildNote(
  source: { journal: string; volume: string; date: string },
  fileName: string
): string {
  const segs = [source.journal, source.volume ? `Volume ${source.volume}` : "", source.date].filter(Boolean);
  return segs.length
    ? `Imported from ${fileName}. ${segs.join(" | ")}.`
    : `Imported from ${fileName}.`;
}

export function parseCitationFile(
  fileName: string,
  content: string,
  format: CitationImportFormat
): Omit<CitationLibrarySource, "decimalId" | "origin" | "fileName" | "importedAt"> {
  let parsed: ParsedCitation;

  switch (format) {
    case "ris":
      parsed = parseRis(content);
      break;
    case "xml":
      parsed = parseEndnoteXml(content);
      break;
    case "enw":
      parsed = parseEnw(content);
      break;
    default: {
      const _exhaustive: never = format;
      throw new Error(`Unsupported format: ${String(_exhaustive)}`);
    }
  }

  const note = buildNote({ journal: parsed.journal, volume: parsed.volume, date: parsed.date }, fileName);

  return {
    type: parsed.type,
    title: parsed.title,
    authors: parsed.authors,
    journal: parsed.journal,
    volume: parsed.volume,
    issue: parsed.issue,
    pages: parsed.pages,
    date: parsed.date,
    year: parsed.year,
    doi: parsed.doi,
    abstract: parsed.abstract,
    isbn: parsed.isbn,
    publisher: parsed.publisher,
    note,
  };
}
