import type { ParsedArtifactCandidate } from "./artifactOcrParser";
import { simplifyForMatch } from "./fuzzyMatch";
import type { CaptureResult, ReviewSampleRecord } from "../types/global";
import type { ScannerLearningRulePayload } from "../types/global";

export type ScannerLearningRules = ScannerLearningRulePayload;

export const DEFAULT_SCANNER_LEARNING_RULES: ScannerLearningRules = {
  textReplacements: {
    "CIT DMG": "CRIT DMG",
    "CRIT DMC": "CRIT DMG",
    "CRIT Rate+Z": "CRIT Rate+2",
    "Energv Recharge": "Energy Recharge",
    "Elemental Masterv": "Elemental Mastery",
    "Equipped;": "Equipped:",
  },
};

export function mergeScannerLearningRules(...rules: Array<Partial<ScannerLearningRules> | null | undefined>): ScannerLearningRules {
  return rules.reduce<ScannerLearningRules>(
    (merged, rule) => ({
      textReplacements: { ...merged.textReplacements, ...(rule?.textReplacements ?? {}) },
    }),
    { textReplacements: { ...DEFAULT_SCANNER_LEARNING_RULES.textReplacements } },
  );
}

export function applyScannerLearningRules(capture: CaptureResult | null, rules?: Partial<ScannerLearningRules> | null) {
  if (!capture?.ocr?.length) return capture;
  const merged = mergeScannerLearningRules(rules);
  const replacements = Object.entries(merged.textReplacements ?? {}).filter(([from]) => from.length > 0);
  if (replacements.length === 0) return capture;

  return {
    ...capture,
    ocr: capture.ocr.map((entry) => ({
      ...entry,
      text: applyTextReplacements(entry.text, replacements),
    })),
  };
}

export function shouldSaveReviewSample(parsed: { confidence: number; notes: string[]; fields: Record<string, { confidence: number }> } | null) {
  if (!parsed) return true;
  if (parsed.confidence < 82) return true;
  const criticalFields = ["name", "slot", "mainStat", "mainValue", "setName"];
  if (criticalFields.some((fieldName) => {
    const field = parsed.fields[fieldName];
    return field ? field.confidence < 70 : false;
  })) return true;
  if (parsed.notes.some((note) => /main stat not confidently parsed|main stat value not confidently parsed|set name not confidently parsed|slot not confidently parsed|artifact name not confidently parsed/i.test(note))) {
    return true;
  }
  if (/Substats look incomplete/i.test(parsed.notes.join(" ")) && (parsed.fields.substats?.confidence ?? 0) < 70) return true;
  return false;
}

export function shouldFlagArtifactForReview(
  parsed: { confidence: number; notes: string[]; fields: Record<string, { confidence: number }>; substats?: string[] } | null,
) {
  if (!parsed) return true;
  if (parsed.confidence < 78) return true;

  const criticalFields = ["name", "slot", "mainStat", "mainValue", "setName"];
  if (criticalFields.some((fieldName) => (parsed.fields[fieldName]?.confidence ?? 0) < 70)) return true;

  if (parsed.notes.some((note) => /main stat not confidently parsed|main stat value not confidently parsed|set name not confidently parsed|slot not confidently parsed|artifact name not confidently parsed/i.test(note))) {
    return true;
  }

  const substatCount = parsed.substats?.length ?? 0;
  const substatConfidence = parsed.fields.substats?.confidence ?? 100;
  if (substatCount === 0) return true;
  if (substatCount < 3 && substatConfidence < 70) return true;

  return false;
}

export function countScannerLearningRules(rules?: Partial<ScannerLearningRules> | null) {
  return Object.keys(rules?.textReplacements ?? {}).length;
}

export function deriveScannerLearningRules(
  capture: CaptureResult | null,
  parsed: ParsedArtifactCandidate | null,
): Partial<ScannerLearningRules> | null {
  if (!capture?.ocr?.length || !parsed) return null;

  const replacements: Record<string, string> = {};
  for (const entry of capture.ocr) {
    const expectedValues = expectedValuesForOcrEntry(entry.id, parsed);
    for (const expected of expectedValues) {
      for (const [from, to] of deriveReplacementPairs(entry.text, expected)) {
        if (!from || !to || simplifyForMatch(from) === simplifyForMatch(to)) continue;
        replacements[from] = to;
      }
    }
  }

  return Object.keys(replacements).length > 0 ? { textReplacements: replacements } : null;
}

export function deriveScannerLearningRulesFromReviewSamples(samples: ReviewSampleRecord[] | null | undefined) {
  const merged: Partial<ScannerLearningRules>[] = [];
  for (const entry of samples ?? []) {
    const capture = normalizeReviewCapture(entry);
    const parsed = normalizeReviewParsed(entry);
    const learned = deriveScannerLearningRules(capture, parsed);
    if (learned) merged.push(learned);
  }
  return mergeScannerLearningRules(...merged);
}

function expectedValuesForOcrEntry(id: string, parsed: ParsedArtifactCandidate) {
  switch (id) {
    case "artifact-title":
      return [parsed.name, parsed.slot];
    case "artifact-main-stat":
      return [parsed.mainStat, parsed.mainValue];
    case "artifact-substats":
      return parsed.substats;
    case "artifact-set-effects":
      return [parsed.setName];
    case "artifact-footer":
      return parsed.equipped && parsed.equipped !== "Not detected" ? [`Equipped: ${parsed.equipped}`, parsed.equipped] : [];
    default:
      return [];
  }
}

function deriveReplacementPairs(rawText: string, expected: string) {
  if (!expected || expected.startsWith("Unknown")) return [];
  const normalizedExpected = normalizeLearningText(expected);
  if (normalizedExpected.length < 4) return [];

  const pairs = new Map<string, string>();
  const lines = rawText
    .split("\n")
    .map((line) => normalizeLearningText(line))
    .filter((line) => line.length >= 3);

  for (const line of lines) {
    const score = similarityScore(line, normalizedExpected);
    if (score >= 0.76 && score < 0.995 && Math.abs(line.length - normalizedExpected.length) <= Math.max(12, Math.round(normalizedExpected.length * 0.45))) {
      pairs.set(line, normalizedExpected);
    }

    const lineWords = tokenizeLearningWords(line);
    const expectedWords = tokenizeLearningWords(normalizedExpected);
    if (lineWords.length === expectedWords.length && lineWords.length > 0 && lineWords.length <= 7) {
      for (let index = 0; index < lineWords.length; index++) {
        const rawWord = lineWords[index];
        const expectedWord = expectedWords[index];
        if (rawWord.length < 4 || expectedWord.length < 4) continue;
        const wordScore = similarityScore(rawWord, expectedWord);
        if (wordScore >= 0.7 && wordScore < 0.995) pairs.set(rawWord, expectedWord);
      }
    }
  }

  return [...pairs.entries()];
}

function applyTextReplacements(text: string, replacements: Array<[string, string]>) {
  return replacements.reduce((current, [from, to]) => current.replace(new RegExp(escapeRegex(from), "gi"), to), text);
}

function normalizeReviewCapture(entry: ReviewSampleRecord): CaptureResult | null {
  const capture = entry.sample?.capture;
  if (!capture?.ocr) return null;
  return {
    id: "review-sample",
    name: capture.name ?? "review-sample",
    width: capture.width ?? 0,
    height: capture.height ?? 0,
    dataUrl: capture.dataUrl ?? "",
    capturedAt: capture.capturedAt ?? entry.savedAt,
    captureTarget: capture.captureTarget,
    detailDataUrl: capture.detailDataUrl,
    inventoryDataUrl: capture.inventoryDataUrl,
    ocr: capture.ocr,
    crops: (capture.crops ?? []).map((crop) => ({
      id: crop.id,
      label: crop.label,
      rect: crop.rect,
      dataUrl: crop.dataUrl ?? "",
    })),
    inventoryGrid: capture.inventoryGrid,
    inventoryCount: capture.inventoryCount,
  };
}

function normalizeReviewParsed(entry: ReviewSampleRecord): ParsedArtifactCandidate | null {
  const parsed = entry.sample?.parsed;
  if (!parsed || typeof parsed !== "object") return null;
  const candidate = parsed as Partial<ParsedArtifactCandidate>;
  if (!candidate.fields || typeof candidate.fields !== "object") return null;
  return candidate as ParsedArtifactCandidate;
}

function normalizeLearningText(text: string) {
  return text.replace(/\s+/g, " ").trim();
}

function tokenizeLearningWords(text: string) {
  return text
    .split(/\s+/)
    .map((part) => part.replace(/^[^A-Za-z0-9%+.-]+|[^A-Za-z0-9%+.-]+$/g, ""))
    .filter(Boolean);
}

function similarityScore(left: string, right: string) {
  const a = simplifyForMatch(left);
  const b = simplifyForMatch(right);
  if (!a || !b) return 0;
  if (a === b) return 1;
  const distance = levenshtein(a, b);
  return Math.max(0, 1 - distance / Math.max(a.length, b.length, 1));
}

function levenshtein(a: string, b: string) {
  const dp = Array.from({ length: a.length + 1 }, () => Array<number>(b.length + 1).fill(0));
  for (let i = 0; i <= a.length; i++) dp[i][0] = i;
  for (let j = 0; j <= b.length; j++) dp[0][j] = j;

  for (let i = 1; i <= a.length; i++) {
    for (let j = 1; j <= b.length; j++) {
      const cost = a[i - 1] === b[j - 1] ? 0 : 1;
      dp[i][j] = Math.min(
        dp[i - 1][j] + 1,
        dp[i][j - 1] + 1,
        dp[i - 1][j - 1] + cost,
      );
    }
  }

  return dp[a.length][b.length];
}

function escapeRegex(value: string) {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
