const { getPaperMetrics } = require("./paper-metrics");
const { rasterToEscPos, imageDataToRaster } = require("./escpos-raster");
const { createLogger } = require("./logger");

const logger = createLogger();
const cache = new Map();

function requireQrCode() {
  try {
    return require("qrcode");
  } catch (e) {
    logger.error("[qr] qrcode package missing — run npm ci --omit=dev");
    return null;
  }
}

function requireJimp() {
  try {
    return require("jimp");
  } catch (e) {
    return null;
  }
}

function truncateQrLabel(url, maxChars) {
  const raw = String(url || "")
    .trim()
    .replace(/^https?:\/\//i, "")
    .replace(/\/$/, "");
  const max = Math.max(4, maxChars || 10);
  if (raw.length <= max) return raw;
  return `${raw.slice(0, Math.max(1, max - 3))}...`;
}

async function qrDataToPngBuffer(data, maxPx) {
  const QRCode = requireQrCode();
  if (!QRCode) return null;
  const text = String(data || "").trim().slice(0, 200);
  if (!text) return null;
  return QRCode.toBuffer(text, {
    type: "png",
    width: maxPx,
    margin: 1,
    errorCorrectionLevel: "M",
    color: { dark: "#000000", light: "#ffffff" },
  });
}

function qrRowLayout(paperWidth, count) {
  const metrics = getPaperMetrics(paperWidth);
  const n = Math.max(1, Math.min(3, count || 1));
  const paperPx = metrics.isWide ? 420 : 300;
  const colWidth = Math.floor(paperPx / n);
  const qrSize = Math.min(metrics.qrMaxPx, colWidth - 10);
  const charsPerCol = Math.max(6, Math.floor(metrics.chars / n));
  return { paperPx, colWidth, qrSize, charsPerCol, count: n, metrics };
}

async function buildQrRowComposite(links, paperWidth = 32) {
  const Jimp = requireJimp();
  if (!Jimp) return null;

  const items = (links || []).filter((l) => l && String(l.url || l).trim());
  if (!items.length) return null;

  const { paperPx, colWidth, qrSize, charsPerCol, count } = qrRowLayout(
    paperWidth,
    items.length,
  );
  const labelHeight = 22;
  const height = qrSize + labelHeight + 6;
  const composite = new Jimp(paperPx, height, 0xffffffff);

  let font;
  try {
    font = await Jimp.loadFont(Jimp.FONT_SANS_8_BLACK);
  } catch {
    font = null;
  }

  for (let i = 0; i < count; i += 1) {
    const item = items[i];
    const url = String(item.url || item).trim();
    const png = await qrDataToPngBuffer(url, qrSize);
    if (!png) continue;
    const qrImg = await Jimp.read(png);
    const x = i * colWidth + Math.floor((colWidth - qrSize) / 2);
    composite.composite(qrImg, x, 0);
    if (font) {
      const label = truncateQrLabel(url, charsPerCol);
      composite.print(
        font,
        i * colWidth,
        qrSize + 4,
        { text: label, alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER },
        colWidth,
      );
    }
  }

  composite.grayscale();
  return {
    image: composite,
    labels: items.map((item) =>
      truncateQrLabel(String(item.url || item).trim(), charsPerCol),
    ),
    layout: { paperPx, colWidth, qrSize, charsPerCol, count },
  };
}

async function buildQrRasterEscPos(data, paperWidth = 32, align = "center") {
  const text = String(data || "").trim().slice(0, 200);
  if (!text) return "";

  const metrics = getPaperMetrics(paperWidth);
  const maxPx = metrics.qrMaxPx;
  const key = `${text}:${maxPx}:${align}`;
  if (cache.has(key)) return cache.get(key);

  const Jimp = requireJimp();
  if (!Jimp) {
    cache.set(key, "");
    return "";
  }

  try {
    const png = await qrDataToPngBuffer(text, maxPx);
    if (!png) {
      cache.set(key, "");
      return "";
    }
    const image = await Jimp.read(png);
    image.grayscale();
    const { width, height } = image.bitmap;
    const { raster, widthPx, heightPx } = imageDataToRaster(
      image.bitmap.data,
      width,
      height,
    );
    const esc = rasterToEscPos(raster, widthPx, heightPx, align);
    logger.info(`[qr] raster ${widthPx}x${heightPx} -> ${esc.length} ESC/POS bytes`);
    cache.set(key, esc);
    return esc;
  } catch (e) {
    logger.error(`[qr] raster conversion failed: ${e.message}`);
    cache.set(key, "");
    return "";
  }
}

async function buildQrRowRasterEscPos(links, paperWidth = 32) {
  const urls = (links || []).map((l) => String(l.url || l).trim()).filter(Boolean);
  if (!urls.length) return "";

  const key = `row:${paperWidth}:${urls.join("|")}`;
  if (cache.has(key)) return cache.get(key);

  try {
    const built = await buildQrRowComposite(
      urls.map((url) => ({ url })),
      paperWidth,
    );
    if (!built) {
      cache.set(key, "");
      return "";
    }
    const { width, height } = built.image.bitmap;
    const { raster, widthPx, heightPx } = imageDataToRaster(
      built.image.bitmap.data,
      width,
      height,
    );
    const esc = rasterToEscPos(raster, widthPx, heightPx, "center");
    logger.info(`[qr-row] ${urls.length} codes ${widthPx}x${heightPx} -> ${esc.length} bytes`);
    cache.set(key, esc);
    return esc;
  } catch (e) {
    logger.error(`[qr-row] failed: ${e.message}`);
    cache.set(key, "");
    return "";
  }
}

async function buildQrPngBuffer(data, paperWidth = 32) {
  const metrics = getPaperMetrics(paperWidth);
  return qrDataToPngBuffer(data, metrics.qrMaxPx);
}

async function buildQrRowPngBuffer(links, paperWidth = 32) {
  const Jimp = requireJimp();
  const built = await buildQrRowComposite(links, paperWidth);
  if (!built || !Jimp) return null;
  return built.image.getBufferAsync(Jimp.MIME_PNG);
}

function formatQrLabelRow(links, paperWidth) {
  const { charsPerCol, count } = qrRowLayout(paperWidth, (links || []).length);
  const labels = (links || [])
    .slice(0, count)
    .map((l) => truncateQrLabel(String(l.url || l).trim(), charsPerCol));
  while (labels.length < count) labels.push("");
  return labels
    .map((label) => label.padEnd(charsPerCol).slice(0, charsPerCol))
    .join("");
}

module.exports = {
  buildQrRasterEscPos,
  buildQrRowRasterEscPos,
  buildQrPngBuffer,
  buildQrRowPngBuffer,
  truncateQrLabel,
  qrRowLayout,
  formatQrLabelRow,
};
