import {
  PdfJsonDocument,
  PdfJsonFont,
} from "@app/tools/pdfTextEditor/pdfTextEditorTypes";

export type FontStatus =
  | "perfect"
  | "embedded-subset"
  | "system-fallback"
  | "missing"
  | "unknown";

export interface FontAnalysis {
  fontId: string;
  baseName: string;
  status: FontStatus;
  embedded: boolean;
  isSubset: boolean;
  isStandard14: boolean;
  hasWebFormat: boolean;
  webFormat?: string;
  subtype?: string;
  encoding?: string;
  warnings: string[];
  suggestions: string[];
}

export interface DocumentFontAnalysis {
  fonts: FontAnalysis[];
  canReproducePerfectly: boolean;
  hasWarnings: boolean;
  summary: {
    perfect: number;
    embeddedSubset: number;
    systemFallback: number;
    missing: number;
    unknown: number;
  };
}

/**
 * Determines if a font name indicates it's a subset font.
 * Subset fonts typically have a 6-character prefix like "ABCDEE+"
 */
const isSubsetFont = (baseName: string | null | undefined): boolean => {
  if (!baseName) return false;
  // Check for common subset patterns: ABCDEF+FontName
  return /^[A-Z]{6}\+/.test(baseName);
};

/**
 * Checks if a font is one of the standard 14 PDF fonts that are guaranteed
 * to be available on all PDF readers
 */
const isStandard14Font = (font: PdfJsonFont): boolean => {
  if (font.standard14Name) return true;

  const baseName = (font.baseName || "").toLowerCase().replace(/[-_\s]/g, "");

  const standard14Patterns = [
    "timesroman",
    "timesbold",
    "timesitalic",
    "timesbolditalic",
    "helvetica",
    "helveticabold",
    "helveticaoblique",
    "helveticaboldoblique",
    "courier",
    "courierbold",
    "courieroblique",
    "courierboldoblique",
    "symbol",
    "zapfdingbats",
  ];

  // Check exact matches or if the base name contains the pattern
  return standard14Patterns.some((pattern) => {
    // Exact match
    if (baseName === pattern) return true;
    // Contains pattern (e.g., "ABCDEF+Helvetica" matches "helvetica")
    if (baseName.includes(pattern)) return true;
    return false;
  });
};

/**
 * Checks if a font has a fallback available on the backend.
 * These fonts are embedded in the Stirling PDF backend and can be used
 * for PDF export even if not in the original PDF.
 *
 * Based on PdfJsonFallbackFontService.java
 */
const hasBackendFallbackFont = (font: PdfJsonFont): boolean => {
  const baseName = (font.baseName || "").toLowerCase().replace(/[-_\s]/g, "");

  // Backend has these font families available (from PdfJsonFallbackFontService)
  const backendFonts = [
    // Liberation fonts (metric-compatible with MS core fonts)
    "arial",
    "helvetica",
    "arimo",
    "times",
    "timesnewroman",
    "tinos",
    "courier",
    "couriernew",
    "cousine",
    "liberation",
    "liberationsans",
    "liberationserif",
    "liberationmono",
    // DejaVu fonts
    "dejavu",
    "dejavusans",
    "dejavuserif",
    "dejavumono",
    "dejavusansmono",
    // Noto fonts
    "noto",
    "notosans",
  ];

  return backendFonts.some((pattern) => {
    if (baseName === pattern) return true;
    if (baseName.includes(pattern)) return true;
    return false;
  });
};

/**
 * Extracts the base font name from a subset font name
 * e.g., "ABCDEF+Arial" -> "Arial"
 */
const extractBaseFontName = (
  baseName: string | null | undefined,
): string | null => {
  if (!baseName) return null;
  const match = baseName.match(/^[A-Z]{6}\+(.+)$/);
  return match ? match[1] : baseName;
};

/**
 * Analyzes a single font to determine if it can be reproduced perfectly
 * Takes allFonts to check if full versions of subset fonts are available
 */
export const analyzeFontReproduction = (
  font: PdfJsonFont,
  allFonts?: PdfJsonFont[],
): FontAnalysis => {
  const fontId = font.id || font.uid || "unknown";
  const baseName = font.baseName || "Unknown Font";
  const isSubset = isSubsetFont(font.baseName);
  const isStandard14 = isStandard14Font(font);
  const hasBackendFallback = hasBackendFallbackFont(font);
  const embedded = font.embedded ?? false;

  // Check available web formats (ordered by preference)
  const webFormats = [
    { key: "webProgram", format: font.webProgramFormat },
    { key: "pdfProgram", format: font.pdfProgramFormat },
    { key: "program", format: font.programFormat },
  ];

  const availableWebFormat = webFormats.find((f) => f.format);
  const hasWebFormat = !!availableWebFormat;
  const webFormat = availableWebFormat?.format || undefined;

  const warnings: string[] = [];
  const suggestions: string[] = [];
  let status: FontStatus = "unknown";

  // Check if we have the full font when this is a subset
  let hasFullFontVersion = false;
  if (isSubset && allFonts) {
    const baseFont = extractBaseFontName(font.baseName);
    if (baseFont) {
      // Look for a non-subset version of this font with a web format
      hasFullFontVersion = allFonts.some((f) => {
        const otherBaseName = extractBaseFontName(f.baseName);
        const isNotSubset = !isSubsetFont(f.baseName);
        const hasFormat = !!(
          f.webProgramFormat ||
          f.pdfProgramFormat ||
          f.programFormat
        );
        const sameBase =
          otherBaseName?.toLowerCase() === baseFont.toLowerCase();
        return sameBase && isNotSubset && hasFormat && (f.embedded ?? false);
      });
    }
  }

  // Analyze font status - focusing on PDF export quality
  if (isStandard14) {
    // Standard 14 fonts are always available in PDF readers - perfect for export!
    status = "perfect";
    suggestions.push(
      "Standard PDF font (Times, Helvetica, or Courier). Always available in PDF readers.",
    );
    suggestions.push(
      "Exported PDFs will render consistently across all PDF readers.",
    );
  } else if (embedded && !isSubset) {
    // Perfect: Fully embedded with complete character set
    status = "perfect";
    suggestions.push(
      "Font is fully embedded. Exported PDFs will reproduce text perfectly, even with edits.",
    );
  } else if (
    embedded &&
    isSubset &&
    (hasFullFontVersion || hasBackendFallback)
  ) {
    // Subset but we have the full font or backend fallback - perfect!
    status = "perfect";
    if (hasFullFontVersion) {
      suggestions.push(
        "Full font version is also available in the document. Exported PDFs can reproduce all characters.",
      );
    } else if (hasBackendFallback) {
      suggestions.push(
        "Backend has the full font available. Exported PDFs can reproduce all characters, including new text.",
      );
    }
  } else if (embedded && isSubset) {
    // Good, but subset: May have missing characters if user adds new text
    status = "embedded-subset";
    warnings.push(
      "This is a subset font - only specific characters are embedded in the PDF.",
    );
    warnings.push(
      "Exported PDFs may have missing characters if you add new text with this font.",
    );
    suggestions.push(
      "Existing text will export correctly. New characters may render as boxes (☐) or fallback glyphs.",
    );
  } else if (!embedded && hasBackendFallback) {
    // Not embedded, but backend has it - perfect for export!
    status = "perfect";
    suggestions.push(
      "Backend has this font available. Exported PDFs will use the backend fallback font.",
    );
    suggestions.push("Text will export correctly with consistent appearance.");
  } else if (!embedded) {
    // Not embedded - must rely on system fonts (risky for export)
    status = "missing";
    warnings.push("Font is not embedded in the PDF.");
    warnings.push(
      "Exported PDFs will substitute with a fallback font, which may look very different.",
    );
    suggestions.push(
      "Consider re-embedding fonts or accepting that the exported PDF will use fallback fonts.",
    );
  } else if (embedded && !hasWebFormat) {
    // Embedded but no web format available (still okay for export)
    status = "perfect";
    suggestions.push(
      "Font is embedded in the PDF. Exported PDFs will reproduce correctly.",
    );
    suggestions.push(
      "Web preview may use a fallback font, but the final PDF export will be accurate.",
    );
  }

  // Additional warnings based on font properties
  if (font.subtype === "Type0" && font.cidSystemInfo) {
    const registry = font.cidSystemInfo.registry || "";
    const ordering = font.cidSystemInfo.ordering || "";
    if (
      registry.includes("Adobe") &&
      (ordering.includes("Identity") || ordering.includes("UCS"))
    ) {
      // CID fonts with Identity encoding are common for Asian languages
      if (!embedded || !hasWebFormat) {
        warnings.push("This CID font may contain Asian or Unicode characters.");
      }
    }
  }

  if (
    font.encoding &&
    !font.encoding.includes("WinAnsiEncoding") &&
    !font.encoding.includes("MacRomanEncoding")
  ) {
    // Custom encodings may cause issues
    if (font.encoding !== "Identity-H" && font.encoding !== "Identity-V") {
      warnings.push(`Custom encoding detected: ${font.encoding}`);
    }
  }

  return {
    fontId,
    baseName,
    status,
    embedded,
    isSubset,
    isStandard14,
    hasWebFormat,
    webFormat,
    subtype: font.subtype || undefined,
    encoding: font.encoding || undefined,
    warnings,
    suggestions,
  };
};

/**
 * Gets fonts used on a specific page
 */
export const getFontsForPage = (
  document: PdfJsonDocument | null,
  pageIndex: number,
): PdfJsonFont[] => {
  if (
    !document?.fonts ||
    !document?.pages ||
    pageIndex < 0 ||
    pageIndex >= document.pages.length
  ) {
    return [];
  }

  const page = document.pages[pageIndex];
  if (!page?.textElements) {
    return [];
  }

  // Get unique font IDs used on this page
  const fontIdsOnPage = new Set<string>();
  page.textElements.forEach((element) => {
    if (element?.fontId) {
      fontIdsOnPage.add(element.fontId);
    }
  });

  // Filter fonts to only those used on this page
  const allFonts = document.fonts.filter(
    (font): font is PdfJsonFont => font !== null && font !== undefined,
  );

  const fontsOnPage = allFonts.filter((font) => {
    // Match by ID
    if (font.id && fontIdsOnPage.has(font.id)) {
      return true;
    }
    // Match by UID
    if (font.uid && fontIdsOnPage.has(font.uid)) {
      return true;
    }
    // Match by page-specific ID (pageNumber:id format)
    if (font.pageNumber === pageIndex + 1 && font.id) {
      const pageSpecificId = `${font.pageNumber}:${font.id}`;
      if (fontIdsOnPage.has(pageSpecificId) || fontIdsOnPage.has(font.id)) {
        return true;
      }
    }
    return false;
  });

  // Deduplicate by base font name to avoid showing the same font multiple times
  const uniqueFonts = new Map<string, PdfJsonFont>();
  fontsOnPage.forEach((font) => {
    const baseName =
      extractBaseFontName(font.baseName) ||
      font.baseName ||
      font.id ||
      "unknown";
    const key = baseName.toLowerCase();

    // Keep the first occurrence, or prefer non-subset over subset
    const existing = uniqueFonts.get(key);
    if (!existing) {
      uniqueFonts.set(key, font);
    } else {
      // Prefer non-subset fonts over subset fonts
      const existingIsSubset = isSubsetFont(existing.baseName);
      const currentIsSubset = isSubsetFont(font.baseName);
      if (existingIsSubset && !currentIsSubset) {
        uniqueFonts.set(key, font);
      }
    }
  });

  return Array.from(uniqueFonts.values());
};

/**
 * Analyzes all fonts in a PDF document (or just fonts for a specific page)
 */
export const analyzeDocumentFonts = (
  document: PdfJsonDocument | null,
  pageIndex?: number,
): DocumentFontAnalysis => {
  if (!document?.fonts || document.fonts.length === 0) {
    return {
      fonts: [],
      canReproducePerfectly: true,
      hasWarnings: false,
      summary: {
        perfect: 0,
        embeddedSubset: 0,
        systemFallback: 0,
        missing: 0,
        unknown: 0,
      },
    };
  }

  const allFonts = document.fonts.filter(
    (font): font is PdfJsonFont => font !== null && font !== undefined,
  );

  // Filter to page-specific fonts if pageIndex is provided
  const fontsToAnalyze =
    pageIndex !== undefined ? getFontsForPage(document, pageIndex) : allFonts;

  if (fontsToAnalyze.length === 0) {
    return {
      fonts: [],
      canReproducePerfectly: true,
      hasWarnings: false,
      summary: {
        perfect: 0,
        embeddedSubset: 0,
        systemFallback: 0,
        missing: 0,
        unknown: 0,
      },
    };
  }

  const fontAnalyses = fontsToAnalyze.map((font) =>
    analyzeFontReproduction(font, allFonts),
  );

  // Calculate summary
  const summary = {
    perfect: fontAnalyses.filter((f) => f.status === "perfect").length,
    embeddedSubset: fontAnalyses.filter((f) => f.status === "embedded-subset")
      .length,
    systemFallback: fontAnalyses.filter((f) => f.status === "system-fallback")
      .length,
    missing: fontAnalyses.filter((f) => f.status === "missing").length,
    unknown: fontAnalyses.filter((f) => f.status === "unknown").length,
  };

  // Can reproduce perfectly ONLY if all fonts are truly perfect (not subsets)
  const canReproducePerfectly = fontAnalyses.every(
    (f) => f.status === "perfect",
  );

  // Has warnings if any font has issues (including subsets)
  const hasWarnings = fontAnalyses.some(
    (f) =>
      f.warnings.length > 0 ||
      f.status === "missing" ||
      f.status === "system-fallback" ||
      f.status === "embedded-subset",
  );

  return {
    fonts: fontAnalyses,
    canReproducePerfectly,
    hasWarnings,
    summary,
  };
};

/**
 * Gets a human-readable description of the font status
 */
export const getFontStatusDescription = (status: FontStatus): string => {
  switch (status) {
    case "perfect":
      return "Fully embedded - perfect reproduction";
    case "embedded-subset":
      return "Embedded (subset) - existing text will render correctly";
    case "system-fallback":
      return "Using system font - appearance may differ";
    case "missing":
      return "Not embedded - will use fallback font";
    case "unknown":
      return "Unknown status";
  }
};

/**
 * Gets a color indicator for the font status
 */
export const getFontStatusColor = (status: FontStatus): string => {
  switch (status) {
    case "perfect":
      return "green";
    case "embedded-subset":
      return "blue";
    case "system-fallback":
      return "yellow";
    case "missing":
      return "red";
    case "unknown":
      return "gray";
  }
};

/**
 * Gets an icon indicator for the font status
 */
export const getFontStatusIcon = (status: FontStatus): string => {
  switch (status) {
    case "perfect":
      return "✓";
    case "embedded-subset":
      return "⚠";
    case "system-fallback":
      return "⚠";
    case "missing":
      return "✗";
    case "unknown":
      return "?";
  }
};
