const htmlInput = document.querySelector("#html-file");
const photoFolderInput = document.querySelector("#photo-folder");
const dropzone = document.querySelector(".dropzone");
const fileName = document.querySelector("#file-name");
const errorBox = document.querySelector("#error");
const results = document.querySelector("#results");
const cardsElement = document.querySelector("#cards");
const schoolInput = document.querySelector("#school");
const labelInput = document.querySelector("#card-label");
const orientationInput = document.querySelector("#orientation");
const orientationNote = document.querySelector("#orientation-note");
const logoInput = document.querySelector("#school-logo");
const logoFileName = document.querySelector("#logo-file-name");
const clearLogoButton = document.querySelector("#clear-logo");
const logoSizeInput = document.querySelector("#logo-size");
const logoSizeValue = document.querySelector("#logo-size-value");
const accentInput = document.querySelector("#accent");
const secondaryInput = document.querySelector("#secondary");
const colorValue = document.querySelector("#color-value");
const secondaryValue = document.querySelector("#secondary-value");
const countElement = document.querySelector("#record-count");
const downloadButton = document.querySelector("#download-all");
const downloadStatus = document.querySelector("#download-status");
const quitButton = document.querySelector("#quit-app");
const closedMessage = document.querySelector("#closed-message");
const helpButton = document.querySelector("#help-button");
const helpDialog = document.querySelector("#help-dialog");
let records = [];
let localPhotoFiles = new Map();
let schoolLogo = null;
let schoolLogoUrl = null;
const BRANDING_KEY = "staff-id-studio-branding-v1";
let serverBranding = {};
let saveTimer = null;

helpButton.addEventListener("click", () => helpDialog.showModal());

if (quitButton) {
  quitButton.addEventListener("click", async () => {
    quitButton.disabled = true;
    try {
      await fetch("/api/shutdown", { method: "POST" });
      document.querySelector("main").hidden = true;
      closedMessage.hidden = false;
    } catch {
      quitButton.disabled = false;
    }
  });
}

function savedBranding() {
  if (Object.keys(serverBranding).length) return serverBranding;
  try {
    return JSON.parse(localStorage.getItem(BRANDING_KEY)) || {};
  } catch {
    return {};
  }
}

function saveBranding(extra = {}) {
  const previous = savedBranding();
  const branding = {
    ...previous,
    school: schoolInput.value.trim(),
    label: labelInput.value.trim(),
    primary: accentInput.value,
    secondary: secondaryInput.value,
    logoSize: Number(logoSizeInput.value),
    orientation: orientationInput.value,
    ...extra
  };
  try {
    localStorage.setItem(BRANDING_KEY, JSON.stringify(branding));
  } catch {
    downloadStatus.textContent = "Branding could not be saved in this browser.";
  }
  serverBranding = branding;
  clearTimeout(saveTimer);
  saveTimer = setTimeout(async () => {
    try {
      const response = await fetch("/api/branding", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(serverBranding)
      });
      if (!response.ok) throw new Error("Save failed");
    } catch {
      downloadStatus.textContent = "Server settings could not be saved; browser backup is active.";
    }
  }, 250);
}

function decodeEntities(text) {
  const textarea = document.createElement("textarea");
  textarea.innerHTML = text;
  return textarea.value.replace(/\u00a0/g, " ").trim();
}

function stripTags(text) {
  return decodeEntities(text.replace(/<[^>]*>/g, " ").replace(/\s+/g, " "));
}

function parseExport(html) {
  const organizationMatches = [...html.matchAll(/class\s*=\s*["']?xl28["']?[^>]*>([\s\S]*?)<\/td>/gi)];
  const nameMatches = [...html.matchAll(/class\s*=\s*["']?xl24["']?[^>]*vertical-align\s*:\s*text-bottom[^>]*>([\s\S]*?)<\/td>/gi)];
  const organization = organizationMatches.length ? stripTags(organizationMatches[0][1]) : "Your Organization";

  const people = [];
  for (let index = 0; index < nameMatches.length; index += 1) {
    const start = nameMatches[index].index;
    const end = index + 1 < nameMatches.length ? nameMatches[index + 1].index : html.length;
    const recordHtml = html.slice(start, end);
    const idMatch = recordHtml.match(/class\s*=\s*["']?xl30["']?[^>]*>([\s\S]*?)<\/td>/i);
    const imageMatch = recordHtml.match(/<(?:img|image)\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/i);
    const name = stripTags(nameMatches[index][1]);
    const id = idMatch
      ? stripTags(idMatch[1]).replace(/^\*|\*$/g, "").trim()
      : "";
    const imageUrl = imageMatch ? decodeEntities(imageMatch[1]) : "";
    if (name && id) {
      people.push({ name, id, imageUrl, image: null, imageFailed: false });
    }
  }
  return { organization, people };
}

async function loadImage(record) {
  if (record.image || record.imageFailed) return;
  try {
    const image = new Image();
    image.decoding = "async";
    if (/^https:\/\/rwfs\.renweb\.com\//i.test(record.imageUrl)) {
      image.src = `/image?url=${encodeURIComponent(record.imageUrl)}`;
    } else {
      const normalizedPath = decodeURIComponent(record.imageUrl)
        .replace(/\\/g, "/")
        .replace(/^\.?\//, "");
      const baseName = normalizedPath.split("/").pop().toLowerCase();
      const file = localPhotoFiles.get(baseName);
      if (!file) throw new Error("Companion photo folder is required");
      record.objectUrl = URL.createObjectURL(file);
      image.src = record.objectUrl;
    }
    await image.decode();
    record.image = image;
  } catch {
    record.imageFailed = true;
  }
}

function roundedRect(context, x, y, width, height, radius) {
  context.beginPath();
  context.roundRect(x, y, width, height, radius);
}

function fitText(context, text, maxWidth, initialSize, weight = 700) {
  let size = initialSize;
  do {
    context.font = `${weight} ${size}px Inter, Arial, sans-serif`;
    size -= 2;
  } while (context.measureText(text).width > maxWidth && size > 25);
}

function drawCover(context, image, x, y, width, height) {
  const scale = Math.max(width / image.width, height / image.height);
  const sw = width / scale;
  const sh = height / scale;
  context.drawImage(image, (image.width - sw) / 2, (image.height - sh) / 2, sw, sh, x, y, width, height);
}

function drawContain(context, image, x, y, width, height) {
  const scale = Math.min(width / image.width, height / image.height);
  const drawWidth = image.width * scale;
  const drawHeight = image.height * scale;
  context.drawImage(image, x + (width - drawWidth) / 2, y + (height - drawHeight) / 2, drawWidth, drawHeight);
}

function trimmedLogoData(image) {
  const source = document.createElement("canvas");
  source.width = image.naturalWidth || image.width;
  source.height = image.naturalHeight || image.height;
  const sourceContext = source.getContext("2d", { willReadFrequently: true });
  sourceContext.drawImage(image, 0, 0);
  const pixels = sourceContext.getImageData(0, 0, source.width, source.height).data;
  let left = source.width;
  let right = -1;
  let top = source.height;
  let bottom = -1;

  for (let y = 0; y < source.height; y += 1) {
    for (let x = 0; x < source.width; x += 1) {
      const offset = (y * source.width + x) * 4;
      const alpha = pixels[offset + 3];
      const nearWhite = pixels[offset] > 246 && pixels[offset + 1] > 246 && pixels[offset + 2] > 246;
      if (alpha > 20 && !nearWhite) {
        left = Math.min(left, x);
        right = Math.max(right, x);
        top = Math.min(top, y);
        bottom = Math.max(bottom, y);
      }
    }
  }

  if (right < left || bottom < top) {
    left = 0;
    top = 0;
    right = source.width - 1;
    bottom = source.height - 1;
  }
  const padding = Math.max(4, Math.round(Math.max(right - left, bottom - top) * .06));
  left = Math.max(0, left - padding);
  top = Math.max(0, top - padding);
  right = Math.min(source.width - 1, right + padding);
  bottom = Math.min(source.height - 1, bottom + padding);

  const cropWidth = right - left + 1;
  const cropHeight = bottom - top + 1;
  const output = document.createElement("canvas");
  const maxSize = 512;
  const scale = Math.min(1, maxSize / Math.max(cropWidth, cropHeight));
  output.width = Math.max(1, Math.round(cropWidth * scale));
  output.height = Math.max(1, Math.round(cropHeight * scale));
  output.getContext("2d").drawImage(source, left, top, cropWidth, cropHeight, 0, 0, output.width, output.height);
  return output.toDataURL("image/png");
}

const CODE39 = {
  "0":"nnnwwnwnn","1":"wnnwnnnnw","2":"nnwwnnnnw","3":"wnwwnnnnn","4":"nnnwwnnnw",
  "5":"wnnwwnnnn","6":"nnwwwnnnn","7":"nnnwnnwnw","8":"wnnwnnwnn","9":"nnwwnnwnn",
  "A":"wnnnnwnnw","B":"nnwnnwnnw","C":"wnwnnwnnn","D":"nnnnwwnnw","E":"wnnnwwnnn",
  "F":"nnwnwwnnn","G":"nnnnnwwnw","H":"wnnnnwwnn","I":"nnwnnwwnn","J":"nnnnwwwnn",
  "-":"nnnwnwnnw","*":"nwnnwnwnn"
};

function drawBarcode(context, value, x, y, maxWidth, height) {
  const safe = value.toUpperCase().replace(/[^0-9A-J-]/g, "");
  const encoded = `*${safe}*`;
  let units = 0;
  for (const char of encoded) units += [...CODE39[char]].reduce((sum, bar) => sum + (bar === "w" ? 3 : 1), 0) + 1;
  const unit = Math.min(3.25, maxWidth / units);
  let cursor = x + (maxWidth - units * unit) / 2;
  context.fillStyle = "#18201e";
  for (const char of encoded) {
    [...CODE39[char]].forEach((widthCode, index) => {
      const width = (widthCode === "w" ? 3 : 1) * unit;
      if (index % 2 === 0) context.fillRect(cursor, y, width, height);
      cursor += width;
    });
    cursor += unit;
  }
}

function drawPlaceholder(context, x, y, width, height) {
  context.fillStyle = "#f8eeee";
  context.fillRect(x, y, width, height);
  context.strokeStyle = "#c62828";
  context.lineWidth = Math.max(18, Math.min(width, height) * .055);
  context.lineCap = "round";
  const inset = Math.min(width, height) * .16;
  context.beginPath();
  context.moveTo(x + inset, y + inset);
  context.lineTo(x + width - inset, y + height - inset);
  context.moveTo(x + width - inset, y + inset);
  context.lineTo(x + inset, y + height - inset);
  context.stroke();
  context.fillStyle = "#9b1c1c";
  context.textAlign = "center";
  context.font = `850 ${Math.max(22, Math.min(width, height) * .07)}px Inter, Arial, sans-serif`;
  context.fillText("PHOTO MISSING", x + width / 2, y + height / 2 + 10);
}

function renderPortraitCard(canvas, record) {
  const context = canvas.getContext("2d");
  const width = 816;
  const height = 1295;
  const accent = accentInput.value;
  const secondary = secondaryInput.value;
  const school = schoolInput.value.trim() || "Your Organization";
  const label = labelInput.value.trim() || "STAFF";
  const logoSize = Number(logoSizeInput.value);
  const logoBoxSize = logoSize + 16;
  const logoBoxX = 118 - logoBoxSize / 2;
  const logoBoxY = 20;

  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
  const scale = Math.min(canvas.width / width, canvas.height / height);
  const offsetX = (canvas.width - width * scale) / 2;
  const offsetY = (canvas.height - height * scale) / 2;
  context.setTransform(scale, 0, 0, scale, offsetX, offsetY);
  context.fillStyle = "#ffffff";
  roundedRect(context, 0, 0, width, height, 34);
  context.fill();
  context.fillStyle = accent;
  context.fillRect(0, 0, width, 28);
  context.fillRect(0, height - 165, width, 165);

  context.fillStyle = secondary;
  context.fillRect(0, 28, width, 10);
  context.fillRect(0, height - 165, width, 10);

  context.textAlign = "left";
  if (schoolLogo) {
    context.save();
    context.fillStyle = "#ffffff";
    roundedRect(context, logoBoxX, logoBoxY, logoBoxSize, logoBoxSize, 19);
    context.fill();
    roundedRect(context, logoBoxX + 8, logoBoxY + 8, logoSize, logoSize, 14);
    context.clip();
    drawContain(context, schoolLogo, logoBoxX + 8, logoBoxY + 8, logoSize, logoSize);
    context.restore();
  } else {
    context.fillStyle = accent;
    roundedRect(context, logoBoxX, logoBoxY, logoBoxSize, logoBoxSize, 19);
    context.fill();
    context.fillStyle = "#ffffff";
    context.font = "850 24px Inter, Arial, sans-serif";
    context.textAlign = "center";
    context.fillText("ID", 118, logoBoxY + logoBoxSize / 2 + 9);
  }
  context.fillStyle = "#18201e";
  context.font = "850 26px Inter, Arial, sans-serif";
  context.textAlign = "right";
  context.fillText(label.toUpperCase(), width - 72, 114);
  context.fillStyle = secondary;
  context.fillRect(width - 156, 132, 84, 5);

  const photoX = 92, photoY = 215, photoW = 630, photoH = 630;
  context.save();
  roundedRect(context, photoX, photoY, photoW, photoH, 28);
  context.clip();
  if (record.image) drawCover(context, record.image, photoX, photoY, photoW, photoH);
  else drawPlaceholder(context, photoX, photoY, photoW, photoH);
  context.restore();

  context.textAlign = "center";
  context.fillStyle = "#18201e";
  fitText(context, record.name, 690, 58, 750);
  context.fillText(record.name, width / 2, 930);
  context.fillStyle = "#707875";
  context.font = "700 20px Inter, Arial, sans-serif";
  context.fillText(`ID ${record.id}`, width / 2, 974);

  drawBarcode(context, record.id, 130, 1015, 554, 72);
  context.fillStyle = "#707875";
  context.font = "600 15px ui-monospace, monospace";
  context.fillText(`*${record.id}*`, width / 2, 1114);

  context.fillStyle = "#ffffff";
  context.textAlign = "center";
  fitText(context, school, 680, 31, 750);
  context.fillText(school, width / 2, height - 93);
  context.font = "600 16px Inter, Arial, sans-serif";
  context.globalAlpha = .72;
  context.fillText("OFFICIAL STAFF IDENTIFICATION", width / 2, height - 54);
  context.globalAlpha = 1;
  context.textAlign = "left";
}

function renderLandscapeCard(canvas, record) {
  const context = canvas.getContext("2d");
  const width = 1295;
  const height = 816;
  const accent = accentInput.value;
  const secondary = secondaryInput.value;
  const school = schoolInput.value.trim() || "Your Organization";
  const label = labelInput.value.trim() || "STAFF";
  const logoSize = Math.min(130, Number(logoSizeInput.value));

  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
  const scale = Math.min(canvas.width / width, canvas.height / height);
  const offsetX = (canvas.width - width * scale) / 2;
  const offsetY = (canvas.height - height * scale) / 2;
  context.setTransform(scale, 0, 0, scale, offsetX, offsetY);

  context.fillStyle = "#ffffff";
  roundedRect(context, 0, 0, width, height, 34);
  context.fill();
  context.fillStyle = accent;
  context.fillRect(0, 0, width, 122);
  context.fillRect(width - 355, 0, 355, height);
  context.fillStyle = secondary;
  context.fillRect(0, 122, width, 10);
  context.fillRect(width - 365, 0, 10, height);

  if (schoolLogo) {
    context.save();
    roundedRect(context, 38, 20, logoSize + 18, logoSize + 18, 18);
    context.clip();
    context.fillStyle = "#ffffff";
    context.fill();
    drawContain(context, schoolLogo, 47, 29, logoSize, logoSize);
    context.restore();
  } else {
    context.fillStyle = "#ffffff";
    roundedRect(context, 38, 24, 78, 78, 16);
    context.fill();
    context.fillStyle = accent;
    context.font = "850 25px Inter, Arial, sans-serif";
    context.textAlign = "center";
    context.fillText("ID", 77, 73);
  }

  context.fillStyle = "#ffffff";
  context.textAlign = "left";
  fitText(context, school, 720, 34, 750);
  context.fillText(school, schoolLogo ? 38 + logoSize + 42 : 142, 76);

  const photoX = 62, photoY = 174, photoW = 430, photoH = 548;
  context.save();
  roundedRect(context, photoX, photoY, photoW, photoH, 26);
  context.clip();
  if (record.image) drawCover(context, record.image, photoX, photoY, photoW, photoH);
  else drawPlaceholder(context, photoX, photoY, photoW, photoH);
  context.restore();

  context.textAlign = "left";
  context.fillStyle = "#18201e";
  fitText(context, record.name, 390, 52, 750);
  context.fillText(record.name, 548, 286);
  context.fillStyle = "#707875";
  context.font = "700 21px Inter, Arial, sans-serif";
  context.fillText(`ID ${record.id}`, 548, 330);
  context.fillStyle = secondary;
  context.fillRect(548, 366, 104, 6);

  drawBarcode(context, record.id, 548, 438, 322, 88);
  context.fillStyle = "#707875";
  context.font = "600 15px ui-monospace, monospace";
  context.textAlign = "center";
  context.fillText(`*${record.id}*`, 709, 557);

  context.fillStyle = "#ffffff";
  context.textAlign = "center";
  fitText(context, label.toUpperCase(), 285, 54, 850);
  context.fillText(label.toUpperCase(), width - 177, 381);
  context.fillStyle = secondary;
  context.fillRect(width - 227, 410, 100, 7);
  context.fillStyle = "#ffffff";
  context.font = "600 16px Inter, Arial, sans-serif";
  context.globalAlpha = .72;
  context.fillText("OFFICIAL IDENTIFICATION", width - 177, 452);
  context.globalAlpha = 1;
  context.textAlign = "left";
}

function renderCard(canvas, record) {
  if (orientationInput.value === "landscape") renderLandscapeCard(canvas, record);
  else renderPortraitCard(canvas, record);
}

function renderAll() {
  const accent = accentInput.value;
  colorValue.textContent = accent.toUpperCase();
  secondaryValue.textContent = secondaryInput.value.toUpperCase();
  logoSizeValue.value = `${logoSizeInput.value} px`;
  orientationNote.textContent = orientationInput.value === "landscape"
    ? "Horizontal CR80 · 300 DPI"
    : "Vertical CR80 · 300 DPI";
  document.documentElement.style.setProperty("--accent", accent);
  document.querySelectorAll(".card-item canvas").forEach((canvas, index) => renderCard(canvas, records[index]));
}

function buildPreviews() {
  cardsElement.replaceChildren();
  records.forEach((record, index) => {
    const figure = document.createElement("figure");
    figure.className = "card-item";
    const shell = document.createElement("div");
    shell.className = "canvas-shell";
    const canvas = document.createElement("canvas");
    const landscape = orientationInput.value === "landscape";
    canvas.width = landscape ? 1012 : 638;
    canvas.height = landscape ? 638 : 1012;
    canvas.dataset.index = index;
    const caption = document.createElement("figcaption");
    const name = document.createElement("span");
    name.textContent = record.name;
    const id = document.createElement("span");
    id.textContent = record.id;
    caption.append(name, id);
    shell.append(canvas);
    figure.append(shell, caption);
    cardsElement.append(figure);
    renderCard(canvas, record);
  });
}

async function importFile(file) {
  await brandingReady;
  errorBox.hidden = true;
  results.hidden = true;
  if (!file || !/\.html?$/i.test(file.name)) {
    errorBox.textContent = "Please choose an HTML file (.html or .htm).";
    errorBox.hidden = false;
    return;
  }
  const html = await file.text();
  const parsed = parseExport(html);
  if (!parsed.people.length) {
    errorBox.textContent = "No staff records were found. This importer expects the StaffPicIDs RenWeb HTML format.";
    errorBox.hidden = false;
    return;
  }
  records = parsed.people;
  const stored = savedBranding();
  schoolInput.value = stored.school || parsed.organization;
  fileName.textContent = file.name;
  countElement.textContent = `${records.length} staff member${records.length === 1 ? "" : "s"}`;
  downloadStatus.textContent = "Loading staff photos…";
  results.hidden = false;
  buildPreviews();
  await Promise.all(records.map(loadImage));
  renderAll();
  const failures = records.filter(record => record.imageFailed).length;
  const relativePhotos = records.filter(record => !/^https:\/\/rwfs\.renweb\.com\//i.test(record.imageUrl)).length;
  downloadStatus.textContent = failures
    ? relativePhotos && !localPhotoFiles.size
      ? `${records.length} staff found. Choose the StaffPicIDs_files folder to load their photos.`
      : `${records.length - failures} photos loaded; ${failures} will use placeholders.`
    : "All photos loaded. Ready to export.";
}

htmlInput.addEventListener("change", () => importFile(htmlInput.files[0]));
photoFolderInput.addEventListener("change", async () => {
  localPhotoFiles = new Map(
    [...photoFolderInput.files]
      .filter(file => file.type.startsWith("image/"))
      .map(file => [file.name.toLowerCase(), file])
  );
  records.forEach(record => {
    if (!record.image) record.imageFailed = false;
  });
  await Promise.all(records.map(loadImage));
  renderAll();
  const loaded = records.filter(record => record.image).length;
  downloadStatus.textContent = `${loaded} of ${records.length} photos loaded from the selected folder.`;
});
["dragenter", "dragover"].forEach(type => dropzone.addEventListener(type, event => {
  event.preventDefault();
  dropzone.classList.add("dragging");
}));
["dragleave", "drop"].forEach(type => dropzone.addEventListener(type, event => {
  event.preventDefault();
  dropzone.classList.remove("dragging");
}));
dropzone.addEventListener("drop", event => importFile(event.dataTransfer.files[0]));
[schoolInput, labelInput, accentInput, secondaryInput, logoSizeInput].forEach(input => input.addEventListener("input", () => {
  saveBranding();
  renderAll();
}));
orientationInput.addEventListener("change", () => {
  saveBranding();
  buildPreviews();
  renderAll();
});

logoInput.addEventListener("change", () => {
  const file = logoInput.files[0];
  if (!file) return;
  if (!["image/png", "image/jpeg"].includes(file.type) || file.size > 8 * 1024 * 1024) {
    errorBox.textContent = "Please choose a PNG or JPG school logo smaller than 8 MB.";
    errorBox.hidden = false;
    logoInput.value = "";
    return;
  }
  errorBox.hidden = true;
  if (schoolLogoUrl) URL.revokeObjectURL(schoolLogoUrl);
  schoolLogoUrl = URL.createObjectURL(file);
  const image = new Image();
  image.onload = () => {
    const storedData = trimmedLogoData(image);
    const storedImage = new Image();
    storedImage.onload = () => {
      schoolLogo = storedImage;
      logoFileName.textContent = file.name;
      clearLogoButton.hidden = false;
      saveBranding({ logo: storedData, logoName: file.name });
      renderAll();
    };
    storedImage.src = storedData;
  };
  image.src = schoolLogoUrl;
});

clearLogoButton.addEventListener("click", () => {
  schoolLogo = null;
  logoInput.value = "";
  logoFileName.textContent = "Choose PNG or JPG";
  clearLogoButton.hidden = true;
  saveBranding({ logo: null, logoName: null });
  renderAll();
});

async function restoreBranding() {
  try {
    const response = await fetch("/api/branding", { cache: "no-store" });
    if (response.ok) serverBranding = await response.json();
  } catch {
    serverBranding = {};
  }
  const stored = savedBranding();
  if (stored.school) schoolInput.value = stored.school;
  if (stored.label) labelInput.value = stored.label;
  if (/^#[0-9a-f]{6}$/i.test(stored.primary || "")) accentInput.value = stored.primary;
  if (/^#[0-9a-f]{6}$/i.test(stored.secondary || "")) secondaryInput.value = stored.secondary;
  if (Number(stored.logoSize) >= 48 && Number(stored.logoSize) <= 160) {
    logoSizeInput.value = String(stored.logoSize);
  }
  if (stored.orientation === "landscape" || stored.orientation === "portrait") {
    orientationInput.value = stored.orientation;
  }
  if (stored.logo) {
    const image = new Image();
    image.onload = () => {
      const trimmedData = trimmedLogoData(image);
      const trimmedImage = new Image();
      trimmedImage.onload = () => {
        schoolLogo = trimmedImage;
        logoFileName.textContent = stored.logoName || "Saved school logo";
        clearLogoButton.hidden = false;
        if (trimmedData !== stored.logo) saveBranding({ logo: trimmedData });
        renderAll();
      };
      trimmedImage.src = trimmedData;
    };
    image.src = stored.logo;
  }
  renderAll();
  if (!Object.keys(serverBranding).length && Object.keys(stored).length) {
    saveBranding(stored);
  }
}

const brandingReady = restoreBranding();

function crc32(bytes) {
  let crc = -1;
  for (const byte of bytes) {
    crc ^= byte;
    for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
  }
  return (crc ^ -1) >>> 0;
}

function makeZip(files) {
  const encoder = new TextEncoder();
  const parts = [];
  const directory = [];
  let offset = 0;
  const u16 = value => new Uint8Array([value & 255, (value >>> 8) & 255]);
  const u32 = value => new Uint8Array([value & 255, (value >>> 8) & 255, (value >>> 16) & 255, (value >>> 24) & 255]);

  files.forEach(file => {
    const name = encoder.encode(file.name);
    const crc = crc32(file.data);
    const local = [u32(0x04034b50), u16(20), u16(0), u16(0), u16(0), u16(0), u32(crc), u32(file.data.length), u32(file.data.length), u16(name.length), u16(0), name, file.data];
    const localLength = local.reduce((sum, part) => sum + part.length, 0);
    parts.push(...local);
    directory.push(u32(0x02014b50), u16(20), u16(20), u16(0), u16(0), u16(0), u16(0), u32(crc), u32(file.data.length), u32(file.data.length), u16(name.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset), name);
    offset += localLength;
  });
  const directorySize = directory.reduce((sum, part) => sum + part.length, 0);
  parts.push(...directory, u32(0x06054b50), u16(0), u16(0), u16(files.length), u16(files.length), u32(directorySize), u32(offset), u16(0));
  return new Blob(parts, { type: "application/zip" });
}

function canvasToJpeg(canvas) {
  return new Promise(resolve => canvas.toBlob(async blob => resolve(new Uint8Array(await blob.arrayBuffer())), "image/jpeg", .94));
}

downloadButton.addEventListener("click", async () => {
  downloadButton.disabled = true;
  renderAll();
  downloadStatus.textContent = "Rendering high-resolution JPEGs…";
  try {
    const files = [];
    const canvases = [...document.querySelectorAll(".card-item canvas")];
    const now = new Date();
    const timestamp = [
      now.getFullYear(),
      String(now.getMonth() + 1).padStart(2, "0"),
      String(now.getDate()).padStart(2, "0"),
      "-",
      String(now.getHours()).padStart(2, "0"),
      String(now.getMinutes()).padStart(2, "0"),
      String(now.getSeconds()).padStart(2, "0")
    ].join("");
    for (let index = 0; index < canvases.length; index += 1) {
      renderCard(canvases[index], records[index]);
      const safeName = records[index].name.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "");
      files.push({ name: `${safeName}-${records[index].id}-${orientationInput.value}-${timestamp}.jpg`, data: await canvasToJpeg(canvases[index]) });
      downloadStatus.textContent = `Rendering ${index + 1} of ${canvases.length}…`;
    }
    const zip = makeZip(files);
    const link = document.createElement("a");
    link.href = URL.createObjectURL(zip);
    link.download = `staff-id-cards-${timestamp}.zip`;
    link.click();
    setTimeout(() => URL.revokeObjectURL(link.href), 2000);
    downloadStatus.textContent = `${files.length} JPEGs exported with ${accentInput.value.toUpperCase()} and ${secondaryInput.value.toUpperCase()}.`;
  } catch {
    downloadStatus.textContent = "The export could not be completed. Please try again.";
  } finally {
    downloadButton.disabled = false;
  }
});
