import { ref } from "vue";
import { useTesseract } from "./useTesseract.js";
export function useMrzReader(options = {}) {
  const scaleFactor = options.scaleFactor ?? 2;
  const useOpenCv = options.useOpenCv ?? true;
  const ocr = useTesseract({
    lang: options.lang ?? "ocrb",
    langPath: options.langPath ?? "/tesseract/"
  });
  const opencvReady = ref(false);
  const opencvTried = ref(false);
  let opencvInstance = null;
  const MRZ_CHARS = /^[A-Z0-9<]+$/;
  const WEIGHTS = [7, 3, 1];
  async function ensureOpenCvReady() {
    if (!useOpenCv || !import.meta.client) return false;
    if (opencvReady.value && opencvInstance) return true;
    if (opencvTried.value) return false;
    opencvTried.value = true;
    const resolveCv = async (mod) => {
      let defaultExport = mod.default;
      if (defaultExport && typeof defaultExport.then === "function") {
        try {
          defaultExport = await defaultExport;
        } catch {
          defaultExport = void 0;
        }
      }
      const first = defaultExport;
      if (first && typeof first.Mat === "function")
        return first;
      const second = mod.cv;
      if (second && typeof second.Mat === "function")
        return second;
      return void 0;
    };
    let importedCv;
    try {
      const mod = await import("@techstark/opencv-js");
      importedCv = await resolveCv(mod);
    } catch {
      importedCv = void 0;
    }
    opencvInstance = importedCv;
    opencvReady.value = !!opencvInstance;
    return opencvReady.value;
  }
  function charValue(ch) {
    if (ch === "<") return 0;
    const code = ch.charCodeAt(0);
    if (code >= 48 && code <= 57) return code - 48;
    if (code >= 65 && code <= 90) return code - 55;
    return -1;
  }
  function checksum(input) {
    let sum = 0;
    for (let i = 0; i < input.length; i++) {
      const value = charValue(input[i]);
      if (value < 0) return void 0;
      sum += value * WEIGHTS[i % 3];
    }
    return String(sum % 10);
  }
  function normalizeMrzText(raw) {
    return raw.toUpperCase().replace(/[^A-Z0-9<\r\n]/g, "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
  }
  function normalizeLine44(line) {
    return line.replace(/[^A-Z0-9<]/g, "").padEnd(44, "<").slice(0, 44);
  }
  function asDigits(text) {
    return text.replace(/[ODQ]/g, "0").replace(/[IL]/g, "1").replace(/Z/g, "2").replace(/S/g, "5").replace(/B/g, "8").replace(/G/g, "6");
  }
  function asLetters(text) {
    return text.replace(/0/g, "O").replace(/1/g, "I").replace(/2/g, "Z").replace(/5/g, "S").replace(/8/g, "B");
  }
  function normalizeCountryCode(code) {
    const clean = code.replace(/</g, "").trim();
    if (!/^[A-Z]{3}$/.test(clean)) return void 0;
    return clean;
  }
  function isValidDateYYMMDD(value) {
    if (!/^\d{6}$/.test(value)) return false;
    const mm = Number(value.slice(2, 4));
    const dd = Number(value.slice(4, 6));
    return mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31;
  }
  function parseNameField(nameField) {
    const clean = nameField.replace(/<+$/g, "");
    const [rawSurname, rawGiven] = clean.split("<<");
    return {
      surname: rawSurname?.replace(/</g, " ").trim() || void 0,
      givenNames: rawGiven?.replace(/</g, " ").trim() || void 0
    };
  }
  function validateTd3Passport(lines, raw) {
    if (lines.length !== 2) return void 0;
    const line1 = normalizeLine44(lines[0]);
    const line2 = normalizeLine44(lines[1]);
    if (!MRZ_CHARS.test(line1) || !MRZ_CHARS.test(line2)) return void 0;
    const documentType = line1.slice(0, 2).replace(/</g, "").replace(/^F/, "P");
    const issuingState = normalizeCountryCode(asLetters(line1.slice(2, 5)));
    const { surname, givenNames } = parseNameField(line1.slice(5, 44));
    const passportNumber = line2.slice(0, 9);
    const passportCheck = asDigits(line2.slice(9, 10));
    const nationality = normalizeCountryCode(asLetters(line2.slice(10, 13)));
    const birthDate = asDigits(line2.slice(13, 19));
    const birthCheck = asDigits(line2.slice(19, 20));
    const sex = line2.slice(20, 21).replace(/</g, "X");
    const expirationDate = asDigits(line2.slice(21, 27));
    const expirationCheck = asDigits(line2.slice(27, 28));
    const personalNumber = line2.slice(28, 42);
    const personalCheck = asDigits(line2.slice(42, 43));
    const compositeCheck = asDigits(line2.slice(43, 44));
    if (!documentType.startsWith("P")) return void 0;
    if (!/^[A-Z0-9<]{9}$/.test(passportNumber)) return void 0;
    if (!/^[A-Z0-9<]{14}$/.test(personalNumber)) return void 0;
    if (!/^\d$/.test(passportCheck) || !/^\d$/.test(birthCheck) || !/^\d$/.test(expirationCheck) || !/^\d$/.test(personalCheck) || !/^\d$/.test(compositeCheck)) return void 0;
    if (!isValidDateYYMMDD(birthDate) || !isValidDateYYMMDD(expirationDate)) return void 0;
    const numberCalc = checksum(passportNumber);
    const birthCalc = checksum(birthDate);
    const expirationCalc = checksum(expirationDate);
    const personalCalc = checksum(personalNumber);
    const compositeCalc = checksum(`${passportNumber}${passportCheck}${birthDate}${birthCheck}${expirationDate}${expirationCheck}${personalNumber}${personalCheck}`);
    if (!numberCalc || !birthCalc || !expirationCalc || !personalCalc || !compositeCalc) return void 0;
    if (numberCalc !== passportCheck) return void 0;
    if (birthCalc !== birthCheck) return void 0;
    if (expirationCalc !== expirationCheck) return void 0;
    if (personalCalc !== personalCheck) return void 0;
    if (compositeCalc !== compositeCheck) return void 0;
    return {
      raw,
      lines: [line1, line2],
      fields: {
        documentType,
        issuingState,
        surname,
        givenNames,
        passportNumber: passportNumber.replace(/<+$/g, ""),
        nationality,
        birthDate,
        sex,
        expirationDate,
        personalNumber: personalNumber.replace(/<+$/g, ""),
        checkDigits: {
          passportNumber: passportCheck,
          birthDate: birthCheck,
          expirationDate: expirationCheck,
          personalNumber: personalCheck,
          composite: compositeCheck
        }
      }
    };
  }
  function copyFrameToCanvas(source) {
    const canvas = document.createElement("canvas");
    const width = source.videoWidth || source.width;
    const height = source.videoHeight || source.height;
    canvas.width = width;
    canvas.height = height;
    canvas.getContext("2d")?.drawImage(source, 0, 0, width, height);
    return canvas;
  }
  function scoreTwoLineTextBand(grayData, width, height) {
    const totalPixels = width * height;
    if (totalPixels < 2e3) return 0;
    let sum = 0;
    for (let i = 0; i < grayData.length; i += 4)
      sum += grayData[i];
    const mean = sum / totalPixels;
    if (mean < 120) return 0;
    const darkThreshold = Math.max(55, mean - 32);
    const rowCounts = new Float32Array(height);
    for (let y = 0; y < height; y++) {
      let count = 0;
      for (let x = 0; x < width; x++) {
        const idx = (y * width + x) * 4;
        if (grayData[idx] < darkThreshold)
          count++;
      }
      rowCounts[y] = count;
    }
    const smooth = new Float32Array(height);
    for (let y = 0; y < height; y++) {
      const a = rowCounts[Math.max(0, y - 1)];
      const b = rowCounts[y];
      const c = rowCounts[Math.min(height - 1, y + 1)];
      smooth[y] = (a + b + c) / 3;
    }
    let peak1 = 0;
    let peak1Idx = -1;
    for (let i = 0; i < height; i++) {
      if (smooth[i] > peak1) {
        peak1 = smooth[i];
        peak1Idx = i;
      }
    }
    let peak2 = 0;
    for (let i = 0; i < height; i++) {
      if (Math.abs(i - peak1Idx) < Math.max(3, Math.floor(height * 0.14)))
        continue;
      if (smooth[i] > peak2)
        peak2 = smooth[i];
    }
    const avg = smooth.reduce((acc, v) => acc + v, 0) / Math.max(1, smooth.length);
    const prominence = peak1 + peak2 - avg * 2;
    if (peak1 < width * 0.2 || peak2 < width * 0.2) return 0;
    return prominence / Math.max(1, width);
  }
  function findMrzRegionCandidates(sourceCanvas) {
    const srcW = sourceCanvas.width;
    const srcH = sourceCanvas.height;
    if (!srcW || !srcH) return [];
    const probeW = Math.min(640, srcW);
    const scale = probeW / srcW;
    const probeH = Math.max(1, Math.floor(srcH * scale));
    const probe = document.createElement("canvas");
    probe.width = probeW;
    probe.height = probeH;
    const ctx = probe.getContext("2d");
    if (!ctx) return [];
    ctx.drawImage(sourceCanvas, 0, 0, srcW, srcH, 0, 0, probeW, probeH);
    const widths = [0.58, 0.68, 0.78, 0.88, 0.94];
    const heights = [0.14, 0.18, 0.22, 0.26];
    const xCenters = [0.5, 0.42, 0.58, 0.34, 0.66];
    const scored = [];
    for (const wRatio of widths) {
      for (const hRatio of heights) {
        const w = Math.max(56, Math.floor(probeW * wRatio));
        const h = Math.max(30, Math.floor(probeH * hRatio));
        const yStart = Math.floor(probeH * 0.24);
        const yEnd = Math.floor(probeH * 0.94);
        const step = Math.max(10, Math.floor(h * 0.28));
        for (const center of xCenters) {
          const x = Math.max(0, Math.min(probeW - w, Math.floor(probeW * center - w / 2)));
          for (let y = yStart; y + h < yEnd; y += step) {
            const img = ctx.getImageData(x, y, w, h);
            const score = scoreTwoLineTextBand(img.data, w, h);
            if (score <= 0) continue;
            scored.push({ x: Math.round(x / scale), y: Math.round(y / scale), w: Math.round(w / scale), h: Math.round(h / scale), score });
          }
        }
      }
    }
    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, 4);
  }
  function findMrzCandidatesWithOpenCv(sourceCanvas) {
    if (!opencvReady.value || !opencvInstance) return [];
    const cv = opencvInstance;
    let src;
    let gray;
    let blur;
    let edges;
    let kernel;
    let closed;
    let contours;
    let hierarchy;
    try {
      src = cv.imread(sourceCanvas);
      gray = new cv.Mat();
      blur = new cv.Mat();
      edges = new cv.Mat();
      kernel = cv.getStructuringElement(cv.MORPH_RECT, new cv.Size(9, 3));
      closed = new cv.Mat();
      contours = new cv.MatVector();
      hierarchy = new cv.Mat();
      cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
      cv.GaussianBlur(gray, blur, new cv.Size(5, 5), 0, 0, cv.BORDER_DEFAULT);
      cv.Canny(blur, edges, 50, 140, 3, false);
      cv.morphologyEx(edges, closed, cv.MORPH_CLOSE, kernel);
      cv.findContours(closed, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
      const output = [];
      for (let i = 0; i < contours.size(); i++) {
        const contour = contours.get(i);
        const rect = cv.boundingRect(contour);
        contour.delete?.();
        const ratio = rect.width / Math.max(1, rect.height);
        if (rect.width < sourceCanvas.width * 0.45 || rect.height < sourceCanvas.height * 0.1) continue;
        if (ratio < 2.2 || ratio > 10) continue;
        output.push({
          x: rect.x,
          y: rect.y,
          w: rect.width,
          h: rect.height,
          score: rect.width * rect.height / (sourceCanvas.width * sourceCanvas.height)
        });
      }
      output.sort((a, b) => b.score - a.score);
      return output.slice(0, 4);
    } catch {
      return [];
    } finally {
      src?.delete?.();
      gray?.delete?.();
      blur?.delete?.();
      edges?.delete?.();
      kernel?.delete?.();
      closed?.delete?.();
      contours?.delete?.();
      hierarchy?.delete?.();
    }
  }
  function applyBinarize(canvas) {
    const ctx = canvas.getContext("2d");
    if (!ctx) return;
    const img = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = img.data;
    let min = 255;
    let max = 0;
    for (let i = 0; i < data.length; i += 4) {
      const g = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
      if (g < min) min = g;
      if (g > max) max = g;
    }
    const range = Math.max(1, max - min);
    const threshold = 0.58 * 255;
    for (let i = 0; i < data.length; i += 4) {
      const g = (0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2] - min) * (255 / range);
      const v = g > threshold ? 255 : 0;
      data[i] = v;
      data[i + 1] = v;
      data[i + 2] = v;
      data[i + 3] = 255;
    }
    ctx.putImageData(img, 0, 0);
  }
  function renderCandidate(outputCanvas, sourceCanvas, candidate, angleDeg = 0, zoom = 1, binarize = true) {
    outputCanvas.width = Math.max(1, Math.floor(candidate.w * zoom));
    outputCanvas.height = Math.max(1, Math.floor(candidate.h * zoom));
    const ctx = outputCanvas.getContext("2d");
    if (!ctx) return;
    ctx.save();
    ctx.translate(outputCanvas.width / 2, outputCanvas.height / 2);
    ctx.rotate(angleDeg * Math.PI / 180);
    ctx.drawImage(
      sourceCanvas,
      candidate.x,
      candidate.y,
      candidate.w,
      candidate.h,
      -outputCanvas.width / 2,
      -outputCanvas.height / 2,
      outputCanvas.width,
      outputCanvas.height
    );
    ctx.restore();
    if (binarize)
      applyBinarize(outputCanvas);
  }
  function pickPassportCandidates(lines) {
    const candidates = [];
    for (let i = 0; i < lines.length - 1; i++) {
      if (lines[i].length < 30 || lines[i + 1].length < 30) continue;
      candidates.push([normalizeLine44(lines[i]), normalizeLine44(lines[i + 1])]);
    }
    if (!candidates.length) {
      const sorted = [...lines].sort((a, b) => b.length - a.length);
      if (sorted.length >= 2)
        candidates.push([normalizeLine44(sorted[0]), normalizeLine44(sorted[1])]);
    }
    return candidates;
  }
  async function decodeFromCanvas(sourceCanvas) {
    await ocr.ensureReady();
    if (useOpenCv)
      await ensureOpenCvReady();
    const candidates = opencvReady.value ? findMrzCandidatesWithOpenCv(sourceCanvas) : findMrzRegionCandidates(sourceCanvas);
    const fallback = {
      x: Math.floor(sourceCanvas.width * 0.05),
      y: Math.floor(sourceCanvas.height * 0.64),
      w: Math.floor(sourceCanvas.width * 0.9),
      h: Math.floor(sourceCanvas.height * 0.24),
      score: 0
    };
    const selected = (candidates.length ? candidates : [fallback]).slice(0, 3);
    const probe = document.createElement("canvas");
    for (const candidate of selected) {
      for (const angle of [0, -6, 6]) {
        for (const binarize of [true, false]) {
          renderCandidate(probe, sourceCanvas, candidate, angle, scaleFactor, binarize);
          const raw = await ocr.recognize(probe);
          const lines = normalizeMrzText(raw);
          const mrzCandidates = pickPassportCandidates(lines);
          for (const mrz of mrzCandidates) {
            const parsed = validateTd3Passport(mrz, raw);
            if (parsed) return parsed;
          }
        }
      }
    }
    return void 0;
  }
  async function decodeFromVideoElement(video) {
    const canvas = copyFrameToCanvas(video);
    return decodeFromCanvas(canvas);
  }
  async function decodeFromImageFile(file) {
    const image = await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject(new Error("Failed to load image"));
        img.src = reader.result;
      };
      reader.onerror = () => reject(new Error("Failed to read file"));
      reader.readAsDataURL(file);
    });
    const canvas = document.createElement("canvas");
    canvas.width = image.naturalWidth;
    canvas.height = image.naturalHeight;
    canvas.getContext("2d")?.drawImage(image, 0, 0);
    return decodeFromCanvas(canvas);
  }
  return {
    ensureOpenCvReady,
    opencvReady,
    ocrProgress: ocr.progress,
    ocrStatus: ocr.status,
    decodeFromVideoElement,
    decodeFromImageFile
  };
}
