import {
  planBibliographyPasses,
  type LatexCompileRequest,
  type LatexCompileResult,
  type LatexCompiler,
  type LatexCompilerCapabilities,
  type LatexCompilerRuntimeDiagnostics,
} from '@learnitive/latex-compiler';
import type { LearnitiveCompiler } from '@learnitive/project-storage';
import { BusyTexBrowserCompiler } from './busytex-adapter';
import {
  fallbackCompilerCapabilities,
  loadCompilerDeploymentStatus,
} from './deployment-status';
import { SwiftLatexBrowserCompiler } from './swiftlatex-adapter';

function infrastructureFailure(error: unknown): boolean {
  if (!(error instanceof Error)) return true;
  return /failed to (?:load|initialize)|runtime|module|asset|network|fetch|import|unavailable/i.test(
    error.message,
  );
}

/** Primary modern compiler with a self-hosted pdfLaTeX fallback. */
export class BrowserLatexCompiler implements LatexCompiler {
  readonly backend = 'busytex' as const;
  private active: LatexCompiler | undefined;
  private capabilities: LatexCompilerCapabilities | undefined;
  private capabilitiesLoading: Promise<LatexCompilerCapabilities> | undefined;

  constructor(
    private readonly primary: LatexCompiler = new BusyTexBrowserCompiler(),
    private readonly fallback: LatexCompiler = new SwiftLatexBrowserCompiler(),
  ) {}

  supports(compiler: LearnitiveCompiler): boolean {
    const deployed = this.capabilities?.engines[compiler];
    if (deployed) return deployed.available;
    // Before the deployment manifest has loaded, only claim the runtime that
    // is physically bundled in every release. Optional engines become
    // available after getCapabilities() confirms their assets are installed.
    return this.fallback.supports(compiler);
  }

  async getCapabilities(signal?: AbortSignal): Promise<LatexCompilerCapabilities> {
    signal?.throwIfAborted();
    if (this.capabilities) return this.capabilities;
    if (!this.capabilitiesLoading) {
      this.capabilitiesLoading = loadCompilerDeploymentStatus(signal).then((status) => {
        this.capabilities = status;
        return status;
      });
    }
    try {
      return await this.capabilitiesLoading;
    } catch {
      const fallback = fallbackCompilerCapabilities();
      this.capabilities = fallback;
      return fallback;
    } finally {
      this.capabilitiesLoading = undefined;
    }
  }

  getRuntimeDiagnostics(): LatexCompilerRuntimeDiagnostics | undefined {
    const active = this.active?.getRuntimeDiagnostics?.();
    if (active) return active;
    const preferred = this.capabilities?.engines.pdflatex.preferredBackend;
    if (preferred === 'swiftlatex') return this.fallback.getRuntimeDiagnostics?.();
    if (preferred === 'busytex') return this.primary.getRuntimeDiagnostics?.();
    return this.primary.getRuntimeDiagnostics?.() ?? this.fallback.getRuntimeDiagnostics?.();
  }

  private downgradePrimaryRuntime(status: LatexCompilerCapabilities, error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    const reason = `BusyTeX runtime unavailable: ${message}`;
    this.capabilities = {
      ...status,
      mode: this.fallback.supports('pdflatex') ? 'swiftlatex-pdflatex' : 'unavailable',
      engines: {
        pdflatex: {
          available: this.fallback.supports('pdflatex'),
          preferredBackend: this.fallback.supports('pdflatex') ? 'swiftlatex' : null,
          reason: this.fallback.supports('pdflatex') ? null : reason,
          supportsSyncTeX: false,
          syncTeXBackend: null,
          supportsBiber: false,
        },
        xelatex: {
          available: false,
          preferredBackend: null,
          reason,
          supportsSyncTeX: false,
          syncTeXBackend: null,
          supportsBiber: false,
        },
        lualatex: {
          available: false,
          preferredBackend: null,
          reason,
          supportsSyncTeX: false,
          syncTeXBackend: null,
          supportsBiber: false,
        },
      },
    };
  }

  async initialize(signal?: AbortSignal): Promise<void> {
    const status = await this.getCapabilities(signal);
    if (!status.engines.pdflatex.available) {
      throw new Error(
        status.engines.pdflatex.reason ?? 'No browser LaTeX compiler is installed',
      );
    }
    if (status.engines.pdflatex.preferredBackend === 'busytex') {
      try {
        this.active = this.primary;
        await this.primary.initialize(signal);
        return;
      } catch (error) {
        signal?.throwIfAborted();
        if (!infrastructureFailure(error) || !this.fallback.supports('pdflatex')) {
          throw error;
        }
        // Physical BusyTeX files are not sufficient evidence that Xe/Lua work.
        // Downgrade the live capability model before falling back so the UI
        // cannot continue advertising engines whose runtime failed its smoke.
        this.downgradePrimaryRuntime(status, error);
        this.active = this.fallback;
        await this.fallback.initialize(signal);
        return;
      } finally {
        this.active = undefined;
      }
    }
    this.active = this.fallback;
    try {
      await this.fallback.initialize(signal);
    } finally {
      this.active = undefined;
    }
  }

  async compile(request: LatexCompileRequest): Promise<LatexCompileResult> {
    let status = await this.getCapabilities(request.signal);
    let engine = status.engines[request.compiler];
    // A previous transient BusyTeX runtime failure downgrades the in-memory
    // capability view for safety. Re-read the deployment manifest before a
    // later Full/Xe/Lua request so terminal/editor compiles can recover in the
    // same page once the runtime problem has been corrected.
    if (
      (!engine.available || (request.compiler === 'pdflatex' && (request.mode ?? 'full') === 'full' && !engine.supportsSyncTeX)) &&
      status.mode === 'swiftlatex-pdflatex'
    ) {
      const refreshed = await loadCompilerDeploymentStatus(request.signal);
      this.capabilities = refreshed;
      status = refreshed;
      engine = refreshed.engines[request.compiler];
    }
    if (!engine.available) {
      throw new Error(
        engine.reason ?? `${request.compiler} is not available in this Learnitive deployment`,
      );
    }

    const mode = request.mode ?? 'full';
    const bibliography = planBibliographyPasses(request.files);
    const requiresBiber = bibliography.biberRequested;
    if (requiresBiber && !engine.supportsBiber) {
      throw new Error(
        'This project requests Biber. Install the BusyTeX 1.4+ full browser compiler profile to compile biblatex/Biber projects in Learnitive.',
      );
    }
    const requiresSyncTeX = request.compiler === 'pdflatex' && mode === 'full';
    if (requiresSyncTeX && !engine.supportsSyncTeX) {
      throw new Error(
        'Full / SyncTeX pdfLaTeX requires the BusyTeX browser compiler. Switch to Fast Preview or install the full compiler profile.',
      );
    }
    // Fast Preview is an explicit user choice for SwiftLaTeX. Do not let a
    // deployed BusyTeX preference silently route Fast Preview back through the
    // full compiler; that made the two modes indistinguishable and meant a
    // BusyTeX runtime failure also broke the advertised SwiftLaTeX fallback.
    const forceFastPreviewFallback =
      request.compiler === 'pdflatex' && mode === 'fast' && !requiresBiber;
    const preferPrimary =
      !forceFastPreviewFallback &&
      (requiresBiber ||
        engine.preferredBackend === 'busytex' ||
        (requiresSyncTeX && engine.syncTeXBackend === 'busytex'));
    if (!preferPrimary) {
      this.active = this.fallback;
      try {
        return await this.fallback.compile({ ...request, mode });
      } finally {
        this.active = undefined;
      }
    }

    this.active = this.primary;
    try {
      return await this.primary.compile({
        ...request,
        // Biber is a BusyTeX-only workflow. If a project was previously set
        // to Fast Preview, promote this compile to Full rather than silently
        // sending a biblatex/Biber project through SwiftLaTeX.
        mode: requiresBiber ? 'full' : mode,
      });
    } catch (error) {
      request.signal?.throwIfAborted();
      if (!infrastructureFailure(error)) {
        throw error;
      }
      if (requiresBiber) {
        const detail = error instanceof Error ? error.message : String(error);
        this.downgradePrimaryRuntime(status, error);
        throw new Error(
          `Biber compilation requires BusyTeX and cannot fall back to SwiftLaTeX: ${detail}`,
          { cause: error },
        );
      }
      if (request.compiler === 'pdflatex' && (request.mode ?? 'full') === 'full') {
        const detail = error instanceof Error ? error.message : String(error);
        this.downgradePrimaryRuntime(status, error);
        throw new Error(
          `Full / SyncTeX pdfLaTeX failed because BusyTeX could not run: ${detail}. Switch this project to Fast Preview to compile with SwiftLaTeX without SyncTeX.`,
          { cause: error },
        );
      }
      if (!this.fallback.supports(request.compiler)) {
        const detail =
          error instanceof Error ? error.message : String(error);
        this.downgradePrimaryRuntime(status, error);
        throw new Error(
          `${request.compiler} BusyTeX runtime failed: ${detail}`,
          { cause: error },
        );
      }
      request.onProgress?.({
        phase: 'initializing',
        message: 'Primary compiler unavailable; using local pdfLaTeX fallback',
      });
      this.active = this.fallback;
      return this.fallback.compile(request);
    } finally {
      this.active = undefined;
    }
  }

  cancel(): void {
    this.active?.cancel();
    this.primary.cancel();
    this.fallback.cancel();
  }

  dispose(): void {
    this.primary.dispose();
    this.fallback.dispose();
    this.active = undefined;
    this.capabilities = undefined;
    this.capabilitiesLoading = undefined;
  }
}
