import {
  safeProjectFilePath,
  type ProjectFilePath,
} from '@learnitive/project-storage';
import type { LatexDiagnostic } from './types';

const FILE_LINE = /^(.+?\.(?:tex|ltx|sty|cls|bib)):(\d+)(?::(\d+))?:\s*(.*)$/i;
const LATEX_ERROR = /^!\s*(?:LaTeX Error:\s*)?(.*)$/;
const LINE_MARKER = /^l\.(\d+)\s*(.*)$/;
const PACKAGE_WARNING = /^(?:Package\s+\S+\s+Warning|LaTeX Warning):\s*(.*)$/;
const OVERFULL = /^((?:Over|Under)full \\[hv]box .*?)$/;

function safeFile(value: string): ProjectFilePath | undefined {
  const normalized = value.trim().replace(/^\.\//, '');
  const result = safeProjectFilePath(normalized);
  return result.ok ? result.path : undefined;
}

function pushUnique(target: LatexDiagnostic[], diagnostic: LatexDiagnostic): void {
  const key = `${diagnostic.severity}|${diagnostic.file ?? ''}|${diagnostic.line ?? ''}|${diagnostic.message}`;
  if (
    target.some(
      (entry) =>
        `${entry.severity}|${entry.file ?? ''}|${entry.line ?? ''}|${entry.message}` === key,
    )
  ) {
    return;
  }
  target.push(diagnostic);
}

/** Conservative parser for common TeX diagnostics; the complete raw log is retained separately. */
export function parseLatexDiagnostics(log: string): LatexDiagnostic[] {
  const diagnostics: LatexDiagnostic[] = [];
  const lines = log.split(/\r?\n/);
  let pendingError: LatexDiagnostic | undefined;
  let currentFile: ProjectFilePath | undefined;

  for (const raw of lines) {
    const line = raw.trimEnd();
    const openFile = line.match(/\(([^()\s]+\.(?:tex|ltx|sty|cls|bib))/i);
    if (openFile?.[1]) {
      currentFile = safeFile(openFile[1]);
    }

    const fileLine = FILE_LINE.exec(line);
    if (fileLine) {
      const file = safeFile(fileLine[1] ?? '');
      const message = (fileLine[4] ?? '').trim() || 'LaTeX diagnostic';
      pushUnique(diagnostics, {
        severity: /warning/i.test(message) ? 'warning' : 'error',
        message,
        ...(file ? { file } : {}),
        line: Number(fileLine[2]),
        ...(fileLine[3] ? { column: Number(fileLine[3]) } : {}),
        raw,
      });
      pendingError = undefined;
      continue;
    }

    const error = LATEX_ERROR.exec(line);
    if (error) {
      pendingError = {
        severity: 'error',
        message: (error[1] ?? 'LaTeX error').trim(),
        ...(currentFile ? { file: currentFile } : {}),
        raw,
      };
      pushUnique(diagnostics, pendingError);
      continue;
    }

    const lineMarker = LINE_MARKER.exec(line.trimStart());
    if (lineMarker && pendingError) {
      pendingError.line = Number(lineMarker[1]);
      if (lineMarker[2]?.trim()) {
        pendingError.raw = `${pendingError.raw ?? ''}\n${raw}`;
      }
      continue;
    }

    const warning = PACKAGE_WARNING.exec(line);
    if (warning) {
      const message = (warning[1] ?? 'LaTeX warning').trim();
      const code = /undefined references?/i.test(message)
        ? 'undefined-reference'
        : /undefined citations?/i.test(message)
          ? 'undefined-citation'
          : undefined;
      pushUnique(diagnostics, {
        severity: 'warning',
        message,
        ...(currentFile ? { file: currentFile } : {}),
        ...(code ? { code } : {}),
        raw,
      });
      continue;
    }

    if (/undefined references?/i.test(line)) {
      pushUnique(diagnostics, {
        severity: 'warning',
        message: line.trim(),
        code: 'undefined-reference',
        raw,
      });
      continue;
    }
    if (/undefined citations?/i.test(line)) {
      pushUnique(diagnostics, {
        severity: 'warning',
        message: line.trim(),
        code: 'undefined-citation',
        raw,
      });
      continue;
    }

    const box = OVERFULL.exec(line);
    if (box) {
      pushUnique(diagnostics, {
        severity: 'warning',
        message: box[1] ?? line,
        code: 'box-warning',
        raw,
      });
    }
  }

  return diagnostics;
}

export function unsupportedBiberDiagnostic(): LatexDiagnostic {
  return {
    severity: 'error',
    message:
      'This project requests Biber. The current browser compiler release supports BibTeX; switch biblatex to backend=bibtex or compile with a Biber-capable engine.',
    code: 'biber-not-supported',
  };
}

export function unsupportedFallbackMakeindexDiagnostic(): LatexDiagnostic {
  return {
    severity: 'error',
    message:
      'This project requires makeindex. Install the primary browser compiler assets to build indexes in Learnitive.',
    code: 'fallback-makeindex-not-supported',
  };
}
