const form = document.getElementById("pixelForm");
const imageInput = document.getElementById("imageInput");
const uploadDropzone = document.getElementById("uploadDropzone");
const uploadFilename = document.getElementById("uploadFilename");
const uploadFileMeta = document.getElementById("uploadFileMeta");
const uploadProgress = document.getElementById("uploadProgress");
const uploadProgressBar = document.getElementById("uploadProgressBar");
const uploadProgressLabel = document.getElementById("uploadProgressLabel");
const widthInput = document.getElementById("widthInput");
const heightInput = document.getElementById("heightInput");
const sizeSummary = document.getElementById("sizeSummary");
const sampleMethod = document.getElementById("sampleMethod");
const refineInput = document.getElementById("refineInput");
const fitContentBoundsToggle = document.getElementById("fitContentBoundsToggle");
const fitContentBoundsChip = document.getElementById("fitContentBoundsChip");
const transparentBackgroundToggle = document.getElementById("transparentBackgroundToggle");
const transparentBackgroundChip = document.getElementById("transparentBackgroundChip");
const generationSummary = document.getElementById("generationSummary");
const quickStartPanel = document.getElementById("quickStartPanel");
const quickImageInput = document.getElementById("quickImageInput");
const quickUploadDropzone = document.getElementById("quickUploadDropzone");
const quickSelectImageButton = document.getElementById("quickSelectImageButton");
const quickUploadName = document.getElementById("quickUploadName");
const quickUploadMeta = document.getElementById("quickUploadMeta");
const quickSizeModeInputs = document.querySelectorAll('input[name="quick_size_mode"]');
const quickSizeModePills = document.querySelectorAll(".quick-choice-pill input[name=\"quick_size_mode\"]");
const quickWidthInput = document.getElementById("quickWidthInput");
const quickHeightInput = document.getElementById("quickHeightInput");
const quickSizePresetButtons = document.querySelectorAll(".quick-size-preset");
const quickSizeHint = document.getElementById("quickSizeHint");
const quickFitContentBoundsToggle = document.getElementById("quickFitContentBoundsToggle");
const quickFitContentBoundsChip = document.getElementById("quickFitContentBoundsChip");
const quickSampleMethod = document.getElementById("quickSampleMethod");
const quickRefineInput = document.getElementById("quickRefineInput");
const quickTransparentBackgroundToggle = document.getElementById("quickTransparentBackgroundToggle");
const quickTransparentBackgroundChip = document.getElementById("quickTransparentBackgroundChip");
const quickGenerateButton = document.getElementById("quickGenerateButton");
const quickExportModeInputs = document.querySelectorAll('input[name="quick_export_mode"]');
const quickExportModePills = document.querySelectorAll(".quick-choice-pill input[name=\"quick_export_mode\"]");
const quickPixelExportPanel = document.getElementById("quickPixelExportPanel");
const quickPixelExportFields = document.getElementById("quickPixelExportFields");
const quickSlicePresetRow = document.getElementById("quickSlicePresetRow");
const quickSlicePresetButtons = document.querySelectorAll(".quick-slice-preset");
const quickPixelWidthInput = document.getElementById("quickPixelWidthInput");
const quickPixelHeightInput = document.getElementById("quickPixelHeightInput");
const quickSlicePixelButton = document.getElementById("quickSlicePixelButton");
const quickSliceButton = document.getElementById("quickSliceButton");
const quickResultDownloadLink = document.getElementById("quickResultDownloadLink");
const quickUploadStep = document.getElementById("quickUploadStep");
const quickSizeStep = document.getElementById("quickSizeStep");
const quickGenerateStep = document.getElementById("quickGenerateStep");
const quickGenerateHint = quickGenerateStep ? quickGenerateStep.querySelector(".inline-note") : null;
const quickSliceStep = document.getElementById("quickSliceStep");
const quickDownloadStep = document.getElementById("quickDownloadStep");
const quickUploadStepState = document.getElementById("quickUploadStepState");
const quickSizeStepState = document.getElementById("quickSizeStepState");
const quickGenerateStepState = document.getElementById("quickGenerateStepState");
const quickSliceStepState = document.getElementById("quickSliceStepState");
const quickDownloadStepState = document.getElementById("quickDownloadStepState");
const quickFlowSourceStage = document.getElementById("quickFlowSourceStage");
const quickFlowSourceImage = document.getElementById("quickFlowSourceImage");
const quickFlowSourcePlaceholder = document.getElementById("quickFlowSourcePlaceholder");
const quickFlowResultStage = document.getElementById("quickFlowResultStage");
const quickFlowResultImage = document.getElementById("quickFlowResultImage");
const quickFlowResultPlaceholder = document.getElementById("quickFlowResultPlaceholder");
const quickFlowFinalStage = document.getElementById("quickFlowFinalStage");
const quickFlowFinalImage = document.getElementById("quickFlowFinalImage");
const quickFlowFinalPlaceholder = document.getElementById("quickFlowFinalPlaceholder");
const sourcePreview = document.getElementById("sourcePreview");
const sourcePlaceholder = document.getElementById("sourcePlaceholder");
const sourceMeta = document.getElementById("sourceMeta");
const resultMeta = document.getElementById("resultMeta");
const resultHint = document.getElementById("resultHint");
const resultZoomMeta = document.getElementById("resultZoomMeta");
const resultCanvas = document.getElementById("resultCanvas");
const resultViewport = document.getElementById("resultViewport");
const resultArtboard = document.getElementById("resultArtboard");
const resultCanvasShell = document.getElementById("resultCanvasShell");
const selectionOverlay = document.getElementById("selectionOverlay");
const selectionBox = document.getElementById("selectionBox");
const selectionModeChip = document.getElementById("selectionModeChip");
const selectionModeToggle = document.getElementById("selectionModeToggle");
const clearSelectionButton = document.getElementById("clearSelectionButton");
const downloadTransparentButton = document.getElementById("downloadTransparentButton");
const selectionSummary = document.getElementById("selectionSummary");
const alphaThreshold = document.getElementById("alphaThreshold");
const alphaThresholdValue = document.getElementById("alphaThresholdValue");
const downloadLink = document.getElementById("downloadLink");
const downloadHint = document.getElementById("downloadHint");
const generateButton = document.getElementById("generateButton");
const statusText = document.getElementById("statusText");
const formStatusDetail = document.getElementById("formStatusDetail");
const zoomRange = document.getElementById("zoomRange");
const zoomValue = document.getElementById("zoomValue");
const sizeModeInputs = document.querySelectorAll('input[name="size_mode"]');
const modePills = document.querySelectorAll("#pixelForm .mode-pill");
const presetButtons = document.querySelectorAll("#pixelForm .preset-chip");
const zoomButtons = document.querySelectorAll(".zoom-chip");
const sourceStage = document.querySelector(".source-stage");
const resultPanel = resultViewport.closest(".preview-panel");
const atlasForm = document.getElementById("atlasForm");
const atlasPixelOutputWidth = document.getElementById("atlasPixelOutputWidth");
const atlasPixelOutputHeight = document.getElementById("atlasPixelOutputHeight");
const sliceAtlasButton = document.getElementById("sliceAtlasButton");
const atlasPreviewImage = document.getElementById("atlasPreviewImage");
const quickProcessedDownloadButton = document.getElementById("quickProcessedDownloadLink");
const quickZipDownloadButton = document.getElementById("quickZipDownloadLink");

const resultContext = resultCanvas.getContext("2d");

let sourceObjectUrl = null;
let latestResult = null;
let lastZoomMode = "manual";
let currentRenderScale = Number(zoomRange.value);
let dragDepth = 0;
let currentUploadFile = null;
let transparentDownloadUrl = null;
let resultLayoutFrame = 0;
let preserveQuickViewportForNextGenerate = false;
let quickDragDepth = 0;

const panState = {
  active: false,
  pointerId: null,
  startX: 0,
  startY: 0,
  scrollLeft: 0,
  scrollTop: 0,
};

const selectionState = {
  active: false,
  pointerId: null,
  start: null,
  rect: null,
};

function selectedSizeMode() {
  const checkedInput = document.querySelector('input[name="size_mode"]:checked');
  return checkedInput ? checkedInput.value : "manual";
}

function contentBoundsCompressionEnabled() {
  return selectedSizeMode() === "auto" || Boolean(fitContentBoundsToggle && fitContentBoundsToggle.checked);
}

function backgroundTransparencyEnabled() {
  return !transparentBackgroundToggle || transparentBackgroundToggle.checked;
}

function syncToggleChipState(chip, input) {
  if (!chip || !input) {
    return;
  }

  chip.classList.toggle("is-active", Boolean(input.checked));
}

function syncAutoSizeModePresentation(mode = selectedSizeMode()) {
  const autoMode = mode === "auto";

  if (fitContentBoundsToggle) {
    fitContentBoundsToggle.checked = false;
    fitContentBoundsToggle.disabled = true;
  }

  if (fitContentBoundsChip) {
    fitContentBoundsChip.hidden = true;
  }

  if (quickFitContentBoundsToggle) {
    quickFitContentBoundsToggle.checked = false;
    quickFitContentBoundsToggle.disabled = true;
  }

  if (quickFitContentBoundsChip) {
    quickFitContentBoundsChip.hidden = true;
  }

  if (sizeSummary && autoMode) {
    sizeSummary.textContent = "当前使用自动识别：会先识别有效范围，再自动推断像素网格。";
  }
}

function buildSizeSummaryText(mode = selectedSizeMode()) {
  const applyContentCompression = contentBoundsCompressionEnabled();

  if (mode === "auto") {
    return applyContentCompression
      ? "当前使用自动识别，并会先按有效范围收紧内容再推断像素网格。"
      : "当前使用自动识别，系统会尝试推断最佳像素网格。";
  }

  const width = widthInput.value || "?";
  const height = heightInput.value || "?";
  return applyContentCompression
    ? `当前准备生成 ${width} x ${height} 的像素画布，并会先按有效范围压缩。`
    : `当前准备生成 ${width} x ${height} 的像素画布。`;
}

function buildQuickSizeHintText(mode = selectedSizeMode()) {
  const applyContentCompression = contentBoundsCompressionEnabled();

  if (mode === "auto") {
    return applyContentCompression
      ? "当前将先识别有效范围，再自动推断像素网格。"
      : "当前将使用自动识别来推断最佳像素网格。";
  }

  const width = quickWidthInput.value || widthInput.value || "?";
  const height = quickHeightInput.value || heightInput.value || "?";
  return applyContentCompression
    ? `当前将先识别有效范围，再压缩到 ${width} x ${height} 生成像素结果。`
    : `当前将按 ${width} x ${height} 生成像素结果。`;
}

function buildGenerationSummaryText() {
  return backgroundTransparencyEnabled()
    ? "生成时会保留已有透明通道，并尽量把与边缘相连的背景色转成透明。"
    : "生成时会保留背景颜色输出，不自动转成透明背景。";
}

function buildQuickGenerateHintText() {
  return backgroundTransparencyEnabled()
    ? "一键生成时会尝试把边缘背景色转成透明，同时保留完整预览和高级参数区。"
    : "一键生成时会保留背景颜色输出，同时保留完整预览和高级参数区。";
}

function buildResultHintText(data) {
  const notes = [];

  if (data.content_crop_applied) {
    const bounds = data.content_bounds || null;
    if (bounds && bounds.width && bounds.height) {
      notes.push(`已按有效范围 ${bounds.width} x ${bounds.height} 压缩`);
    } else {
      notes.push("已按有效范围压缩");
    }
  }

  if (data.has_transparent_background) {
    notes.push("背景已转透明");
  } else if (data.background_transparency_enabled) {
    notes.push("未检测到需要转透明的边缘背景");
  } else {
    notes.push("当前保留背景颜色");
  }

  notes.push("可继续预览、下载或送去切片");
  return `${notes.join("，")}。`;
}

function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value));
}

function clampZoom(value) {
  const min = Number(zoomRange.min);
  const max = Number(zoomRange.max);
  return clamp(Math.round(value), min, max);
}

function safeJsonParse(text) {
  try {
    return JSON.parse(text);
    if (!atlasWorkflowPrepared && !statusText.classList.contains("status-error")) {
      setStatus("生成完成", "success", "像素结果已经生成，可以继续预览、下载 PNG，或送去下方切片模块。");
    }
  } catch (error) {
    return null;
  }
}

function formatFileSize(sizeInBytes) {
  if (sizeInBytes < 1024 * 1024) {
    return `${(sizeInBytes / 1024).toFixed(1)} KB`;
  }

  return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;
}

function safeJsonParse(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    return null;
  }
}

function buildResultHintText(data) {
  const notes = [];

  if (data && data.content_crop_applied) {
    const bounds = data.content_bounds || null;
    if (bounds && bounds.width && bounds.height) {
      notes.push(`已按有效范围 ${bounds.width} x ${bounds.height} 压缩`);
    } else {
      notes.push("已按有效范围压缩");
    }
  }

  if (data && data.has_transparent_background) {
    notes.push("背景已转透明");
  } else if (data && data.background_transparency_enabled) {
    notes.push("未检测到需要转透明的边缘背景");
  } else {
    notes.push("当前保留背景颜色");
  }

  notes.push("可继续预览、下载或送去切片");
  return `${notes.join("，")}。`;
}

function setStatus(message, type = "idle", detail = "") {
  const fallbackDetail = {
    idle: "准备好图片后，就可以开始生成。",
    loading: "系统正在处理中，请稍等。",
    success: "像素结果已经准备好，可以继续预览或下载。",
    error: "请调整参数或更换图片后再试一次。",
  };

  statusText.textContent = message;
  statusText.className = `status-badge status-${type}`;
  formStatusDetail.textContent = detail || fallbackDetail[type] || fallbackDetail.idle;
}

function setUploadProgress(options = {}) {
  const {
    visible = false,
    value = 0,
    label = "等待上传",
    state = "idle",
    indeterminate = false,
  } = options;

  uploadProgress.classList.toggle("is-visible", visible);
  uploadProgress.dataset.state = state;
  uploadProgressLabel.textContent = label;
  uploadProgressBar.classList.toggle("is-indeterminate", indeterminate);
  uploadProgressBar.style.width = `${clamp(value, 0, 100)}%`;
}

function syncZoomRangeVisual() {
  const min = Number(zoomRange.min);
  const max = Number(zoomRange.max);
  const current = Number(zoomRange.value);
  const percent = ((current - min) / (max - min)) * 100;
  zoomRange.style.setProperty("--range-fill", `${percent}%`);
}

function syncModePills() {
  for (const pill of modePills) {
    const input = pill.querySelector("input");
    pill.classList.toggle("is-active", Boolean(input && input.checked));
  }
}

function syncPresetButtons() {
  const isManual = selectedSizeMode() === "manual";

  for (const button of presetButtons) {
    const matched = isManual
      && button.dataset.width === widthInput.value
      && button.dataset.height === heightInput.value;

    button.classList.toggle("is-active", matched);
    button.disabled = !isManual;
  }
}

function updateSizeSummary() {
  sizeSummary.textContent = buildSizeSummaryText();
  return;

  if (selectedSizeMode() === "auto") {
    sizeSummary.textContent = "当前使用自动识别，系统会尝试推断最佳像素网格。";
    return;
  }

  const width = widthInput.value || "?";
  const height = heightInput.value || "?";
  sizeSummary.textContent = `当前准备生成 ${width} x ${height} 的像素画布。`;
}

function toggleManualInputs() {
  const isManual = selectedSizeMode() === "manual";
  widthInput.disabled = !isManual;
  heightInput.disabled = !isManual;

  syncAutoSizeModePresentation();
  syncModePills();
  syncPresetButtons();
  syncToggleChipState(fitContentBoundsChip, fitContentBoundsToggle);
  syncToggleChipState(transparentBackgroundChip, transparentBackgroundToggle);
  if (generationSummary) {
    generationSummary.textContent = buildGenerationSummaryText();
  }
  updateSizeSummary();
  syncQuickStartFromMain();
}

function selectedAtlasExportModeValue() {
  const checkedInput = document.querySelector('input[name="export_mode"]:checked');
  return checkedInput ? checkedInput.value : "pixel";
}

function syncQuickChoiceStates(nodeList) {
  for (const input of nodeList) {
    const pill = input.closest(".quick-choice-pill");
    if (pill) {
      pill.classList.toggle("is-active", Boolean(input.checked));
    }
  }
}

function syncQuickUploadSummary() {
  if (!quickStartPanel) {
    return;
  }

  const file = getCurrentUploadFile();
  if (quickUploadDropzone) {
    quickUploadDropzone.classList.toggle("has-file", Boolean(file));
  }

  if (!file) {
    quickUploadName.textContent = "还没有选择图片";
    quickUploadMeta.textContent = "支持 PNG / JPG / JPEG / WebP";
    return;
  }

  quickUploadName.textContent = file.name;
  quickUploadMeta.textContent = formatFileSize(file.size);
}

function syncQuickSizeControls() {
  if (!quickStartPanel) {
    return;
  }

  const mode = selectedSizeMode();
  syncAutoSizeModePresentation(mode);
  for (const input of quickSizeModeInputs) {
    input.checked = input.value === mode;
  }
  syncQuickChoiceStates(quickSizeModePills);

  quickWidthInput.value = widthInput.value || "";
  quickHeightInput.value = heightInput.value || "";
  const usingManual = mode === "manual";
  quickWidthInput.disabled = !usingManual;
  quickHeightInput.disabled = !usingManual;
  if (quickFitContentBoundsToggle) {
    quickFitContentBoundsToggle.checked = contentBoundsCompressionEnabled();
  }
  syncToggleChipState(quickFitContentBoundsChip, quickFitContentBoundsToggle);

  for (const button of quickSizePresetButtons) {
    const matched = usingManual
      && button.dataset.width === quickWidthInput.value
      && button.dataset.height === quickHeightInput.value;
    button.classList.toggle("is-active", matched);
    button.disabled = !usingManual;
  }

  quickSizeHint.textContent = buildQuickSizeHintText(mode);
  syncQuickWorkflowStates();
  return;

  quickSizeHint.textContent = usingManual
    ? `当前将按 ${quickWidthInput.value || "?"} x ${quickHeightInput.value || "?"} 生成像素结果。`
    : "当前将使用自动识别来推断最佳像素网格。";
  syncQuickWorkflowStates();
}

function syncQuickGenerateControls() {
  if (!quickStartPanel) {
    return;
  }

  if (quickSampleMethod && sampleMethod) {
    quickSampleMethod.value = sampleMethod.value || "center";
  }

  if (quickRefineInput && refineInput) {
    quickRefineInput.value = refineInput.value || "0.25";
  }

  if (quickTransparentBackgroundToggle) {
    quickTransparentBackgroundToggle.checked = backgroundTransparencyEnabled();
  }
  syncToggleChipState(quickTransparentBackgroundChip, quickTransparentBackgroundToggle);

  if (generationSummary) {
    generationSummary.textContent = buildGenerationSummaryText();
  }

  if (quickGenerateHint) {
    quickGenerateHint.textContent = buildQuickGenerateHintText();
  }
}

function syncQuickExportControls() {
  if (!quickStartPanel) {
    return;
  }

  const exportMode = selectedAtlasExportModeValue();
  for (const input of quickExportModeInputs) {
    input.checked = input.value === exportMode;
  }
  syncQuickChoiceStates(quickExportModePills);

  if (atlasPixelOutputWidth) {
    quickPixelWidthInput.value = atlasPixelOutputWidth.value || "16";
  }

  if (atlasPixelOutputHeight) {
    quickPixelHeightInput.value = atlasPixelOutputHeight.value || "16";
  }

  const usingPixel = exportMode === "pixel";
  if (quickPixelExportPanel) {
    quickPixelExportPanel.classList.toggle("is-hidden", !usingPixel);
  }
  quickPixelWidthInput.disabled = !usingPixel;
  quickPixelHeightInput.disabled = !usingPixel;

  for (const button of quickSlicePresetButtons) {
    const size = button.dataset.size || "";
    const matched = usingPixel && size === quickPixelWidthInput.value && size === quickPixelHeightInput.value;
    button.classList.toggle("is-active", matched);
    button.disabled = !usingPixel;
  }

  syncQuickWorkflowStates();
}

function applyLinkedQuickSliceSize(size) {
  const parsedSize = Number.parseInt(String(size || "").trim(), 10);
  if (!Number.isFinite(parsedSize) || parsedSize <= 0) {
    return;
  }

  const normalized = String(parsedSize);
  setAtlasExportMode("pixel");
  quickPixelWidthInput.value = normalized;
  quickPixelHeightInput.value = normalized;

  if (atlasPixelOutputWidth && atlasPixelOutputHeight) {
    atlasPixelOutputWidth.value = normalized;
    atlasPixelOutputHeight.value = normalized;
    atlasPixelOutputWidth.dispatchEvent(new Event("input", { bubbles: true }));
    atlasPixelOutputHeight.dispatchEvent(new Event("input", { bubbles: true }));
  }

  syncQuickExportControls();
}

function syncQuickResultDownload() {
  if (!quickStartPanel || !quickResultDownloadLink) {
    return;
  }

  const href = downloadLink.getAttribute("href");
  const downloadName = downloadLink.getAttribute("download");
  const isDisabled = downloadLink.classList.contains("disabled") || !href || href === "#";

  if (isDisabled) {
    quickResultDownloadLink.classList.add("disabled");
    quickResultDownloadLink.href = "#";
    quickResultDownloadLink.removeAttribute("download");
    syncQuickWorkflowStates();
    return;
  }

  quickResultDownloadLink.classList.remove("disabled");
  quickResultDownloadLink.href = href;
  if (downloadName) {
    quickResultDownloadLink.download = downloadName;
  } else {
    quickResultDownloadLink.removeAttribute("download");
  }
  syncQuickWorkflowStates();
}

function hasEnabledLink(link) {
  if (!link) {
    return false;
  }

  const href = link.getAttribute("href");
  return !link.classList.contains("disabled") && Boolean(href) && href !== "#";
}

function setQuickStepState(stepNode, statusNode, workflowState, label) {
  if (stepNode) {
    stepNode.dataset.workflowState = workflowState;
  }

  if (statusNode) {
    statusNode.dataset.state = workflowState;
    statusNode.textContent = label;
  }
}

function syncQuickFlowStage(stageNode, imageNode, placeholderNode, options = {}) {
  if (!stageNode || !imageNode || !placeholderNode) {
    return;
  }

  const { src = "", alt = "", placeholder = "" } = options;
  const ready = Boolean(src);

  stageNode.classList.toggle("is-ready", ready);

  if (ready) {
    imageNode.src = src;
    imageNode.alt = alt || imageNode.alt;
    placeholderNode.textContent = placeholder;
    return;
  }

  imageNode.removeAttribute("src");
  placeholderNode.textContent = placeholder;
}

function syncQuickFlowPreviews() {
  if (!quickStartPanel) {
    return;
  }

  const sourceSrc = sourcePreview.getAttribute("src") || "";
  const sourceReady = Boolean(sourceSrc);
  const resultSrc = sourceReady
    ? (
      (latestResult && latestResult.image && latestResult.image.src)
      || downloadLink.getAttribute("href")
      || ""
    )
    : "";
  let finalSrc = "";
  let finalPlaceholder = "等待导出";

  if (!sourceReady) {
    finalSrc = "";
    finalPlaceholder = "等待导出";
  } else if (hasEnabledLink(quickProcessedDownloadButton)) {
    finalSrc = quickProcessedDownloadButton.getAttribute("href") || "";
    finalPlaceholder = "最终导出预览";
  } else if (atlasPreviewImage && atlasPreviewImage.getAttribute("src")) {
    finalSrc = atlasPreviewImage.getAttribute("src") || "";
    finalPlaceholder = hasEnabledLink(quickZipDownloadButton) ? "切片已完成" : "等待导出";
  } else if (hasEnabledLink(quickZipDownloadButton) && resultSrc) {
    finalSrc = resultSrc;
    finalPlaceholder = "切片已完成";
  }

  syncQuickFlowStage(quickFlowSourceStage, quickFlowSourceImage, quickFlowSourcePlaceholder, {
    src: sourceSrc,
    alt: "步骤1 原图预览",
    placeholder: "等待上传",
  });

  syncQuickFlowStage(quickFlowResultStage, quickFlowResultImage, quickFlowResultPlaceholder, {
    src: resultSrc,
    alt: "步骤3 像素结果预览",
    placeholder: "等待生成",
  });

  syncQuickFlowStage(quickFlowFinalStage, quickFlowFinalImage, quickFlowFinalPlaceholder, {
    src: finalSrc,
    alt: "步骤5 最终导出预览",
    placeholder: finalPlaceholder,
  });
}

function syncQuickWorkflowStates() {
  if (!quickStartPanel) {
    return;
  }

  const file = getCurrentUploadFile();
  const sizeReady = selectedSizeMode() === "auto"
    || (Number(quickWidthInput.value) > 0 && Number(quickHeightInput.value) > 0);
  const resultReady = hasEnabledLink(downloadLink);
  const processedReady = hasEnabledLink(document.getElementById("quickProcessedDownloadLink"));
  const zipReady = hasEnabledLink(document.getElementById("quickZipDownloadLink"));
  const currentExportMode = selectedAtlasExportModeValue();

  setQuickStepState(
    quickUploadStep,
    quickUploadStepState,
    file ? "complete" : "idle",
    file ? "已上传" : "待上传",
  );

  setQuickStepState(
    quickSizeStep,
    quickSizeStepState,
    sizeReady ? "complete" : "idle",
    sizeReady ? "已设置" : "待设置",
  );

  setQuickStepState(
    quickGenerateStep,
    quickGenerateStepState,
    resultReady ? "complete" : (file && sizeReady ? "active" : "idle"),
    resultReady ? "已生成" : (file && sizeReady ? "待生成" : "等待上一步"),
  );

  setQuickStepState(
    quickSliceStep,
    quickSliceStepState,
    zipReady ? "complete" : (resultReady ? "active" : "idle"),
    zipReady ? "已切片" : (resultReady ? (currentExportMode === "pixel" ? "待切像素" : "待切原图") : "待切片"),
  );

  setQuickStepState(
    quickDownloadStep,
    quickDownloadStepState,
    (resultReady || processedReady || zipReady) ? "complete" : "idle",
    (resultReady || processedReady || zipReady) ? "可下载" : "待下载",
  );
}

function syncQuickStartFromMain() {
  if (!quickStartPanel) {
    return;
  }

  syncQuickUploadSummary();
  syncQuickSizeControls();
  syncQuickGenerateControls();
  syncQuickExportControls();
  syncQuickResultDownload();
  syncQuickWorkflowStates();
  syncQuickFlowPreviews();
}

function setMainSizeMode(mode) {
  const target = document.querySelector(`#pixelForm input[name="size_mode"][value="${mode}"]`);
  if (!target) {
    return;
  }

  target.checked = true;
  target.dispatchEvent(new Event("change", { bubbles: true }));
}

function setAtlasExportMode(mode) {
  const target = document.querySelector(`input[name="export_mode"][value="${mode}"]`);
  if (!target) {
    return;
  }

  target.checked = true;
  target.dispatchEvent(new Event("change", { bubbles: true }));
}

function syncQuickValueToMain(sourceInput, targetInput) {
  if (!sourceInput || !targetInput) {
    return;
  }

  targetInput.value = sourceInput.value;
  targetInput.dispatchEvent(new Event("input", { bubbles: true }));
}

function syncQuickCheckedToMain(sourceInput, targetInput) {
  if (!sourceInput || !targetInput) {
    return;
  }

  targetInput.checked = Boolean(sourceInput.checked);
  targetInput.dispatchEvent(new Event("change", { bubbles: true }));
}

function revokeSourcePreview() {
  if (sourceObjectUrl) {
    URL.revokeObjectURL(sourceObjectUrl);
    sourceObjectUrl = null;
  }
}

function revokeTransparentDownloadUrl() {
  if (transparentDownloadUrl) {
    URL.revokeObjectURL(transparentDownloadUrl);
    transparentDownloadUrl = null;
  }
}

function getCurrentUploadFile() {
  if (currentUploadFile) {
    return currentUploadFile;
  }

  if (imageInput.files && imageInput.files.length) {
    return imageInput.files[0];
  }

  return null;
}

function trySyncNativeInput(file) {
  if (!file || typeof DataTransfer === "undefined") {
    return;
  }

  try {
    const transfer = new DataTransfer();
    transfer.items.add(file);
    imageInput.files = transfer.files;
  } catch (error) {
    // Some browsers do not allow assigning FileList programmatically.
  }
}

function setDropzoneFileState(hasFile) {
  uploadDropzone.classList.toggle("has-file", hasFile);
}

function resetDropzoneHint() {
  const file = getCurrentUploadFile();

  if (file) {
    uploadFilename.textContent = `已载入 ${file.name}`;
    return;
  }

  uploadFilename.textContent = "当前还没有选择图片";
  uploadFileMeta.textContent = "支持 PNG、JPG、JPEG、WebP";
}

function updateSourcePreview() {
  const file = getCurrentUploadFile();
  revokeSourcePreview();
  setUploadProgress();

  if (!file) {
    sourcePreview.removeAttribute("src");
    sourcePreview.style.display = "none";
    sourcePlaceholder.style.display = "block";
    sourceMeta.textContent = "还没有选择图片";
    uploadFilename.textContent = "当前还没有选择图片";
    uploadFileMeta.textContent = "支持 PNG、JPG、JPEG、WebP";
    setDropzoneFileState(false);
    sourceStage.classList.remove("is-ready");
    syncQuickUploadSummary();
    syncQuickFlowPreviews();
    return;
  }

  setDropzoneFileState(true);
  sourceMeta.textContent = "正在读取图片...";
  uploadFilename.textContent = `已载入 ${file.name}`;
  uploadFileMeta.textContent = `${formatFileSize(file.size)} · 已准备生成`;

  sourceObjectUrl = URL.createObjectURL(file);
  syncQuickUploadSummary();
  sourcePreview.onload = () => {
    const dimensions = `${sourcePreview.naturalWidth} x ${sourcePreview.naturalHeight}`;
    sourceMeta.textContent = `${file.name} · ${dimensions} · ${formatFileSize(file.size)}`;
    uploadFileMeta.textContent = `${dimensions} · ${formatFileSize(file.size)} · 已准备生成`;
    sourceStage.classList.add("is-ready");
    syncQuickFlowPreviews();
  };
  sourcePreview.onerror = () => {
    sourceMeta.textContent = "图片预览失败";
    uploadFileMeta.textContent = "请重新选择一张可读取的图片";
    sourceStage.classList.remove("is-ready");
    setStatus("图片读取失败", "error", "请换一张 PNG、JPG 或 WebP 图片。");
  };
  sourcePreview.src = sourceObjectUrl;
  sourcePreview.style.display = "block";
  sourcePlaceholder.style.display = "none";
  syncQuickFlowPreviews();

  setStatus("图片已载入", "idle", "现在可以调整参数后生成像素图。");
}

function drawEmptyCanvas(message) {
  const width = 420;
  const height = 300;
  resultCanvas.width = width;
  resultCanvas.height = height;
  resultCanvas.style.width = `${width}px`;
  resultCanvas.style.height = `${height}px`;
  resultCanvasShell.style.width = `${width}px`;
  resultCanvasShell.style.height = `${height}px`;
  resultArtboard.style.removeProperty("min-height");
  resultContext.clearRect(0, 0, width, height);

  resultContext.fillStyle = "#fffaf2";
  resultContext.fillRect(0, 0, width, height);

  resultContext.strokeStyle = "rgba(20, 106, 115, 0.08)";
  for (let x = 0; x <= width; x += 22) {
    resultContext.beginPath();
    resultContext.moveTo(x, 0);
    resultContext.lineTo(x, height);
    resultContext.stroke();
  }
  for (let y = 0; y <= height; y += 22) {
    resultContext.beginPath();
    resultContext.moveTo(0, y);
    resultContext.lineTo(width, y);
    resultContext.stroke();
  }

  resultContext.fillStyle = "#607178";
  resultContext.font = '16px "IBM Plex Sans", "Segoe UI", sans-serif';
  resultContext.textAlign = "center";
  resultContext.textBaseline = "middle";
  resultContext.fillText(message, width / 2, height / 2);
}

function setPreviewBusy(isBusy) {
  resultViewport.classList.toggle("is-busy", isBusy);
}

function formatScale(value) {
  if (!Number.isFinite(value) || value <= 0) {
    return "1";
  }

  if (value >= 10 || Math.abs(value - Math.round(value)) < 0.05) {
    return String(Math.round(value * 10) / 10).replace(/\.0$/, "");
  }

  if (value >= 1) {
    return value.toFixed(1).replace(/\.0$/, "");
  }

  return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
}

function getResultFitMetrics() {
  const style = window.getComputedStyle(resultArtboard);
  const paddingX = Number.parseFloat(style.paddingLeft) + Number.parseFloat(style.paddingRight);
  const paddingY = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
  const availableWidth = Math.max(1, resultArtboard.clientWidth - paddingX);
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
  const viewportTop = Math.max(0, resultViewport.getBoundingClientRect().top);
  const availableHeight = Math.max(160, viewportHeight - viewportTop - 40 - paddingY);
  const stageMinHeight = Math.max(288, availableHeight + paddingY);

  return {
    availableWidth,
    availableHeight,
    stageMinHeight,
  };
}

function centerResultViewport() {
  window.requestAnimationFrame(() => {
    const left = Math.max(0, (resultArtboard.scrollWidth - resultArtboard.clientWidth) / 2);
    const top = Math.max(0, (resultArtboard.scrollHeight - resultArtboard.clientHeight) / 2);
    resultArtboard.scrollLeft = left;
    resultArtboard.scrollTop = top;
    syncResultViewportState();
  });
}

function scheduleResultViewportSync() {
  if (resultLayoutFrame) {
    return;
  }

  resultLayoutFrame = window.requestAnimationFrame(() => {
    resultLayoutFrame = 0;

    if (latestResult && lastZoomMode === "fit") {
      fitZoomToViewport();
      return;
    }

    renderSelectionBox();
  });
}

function canPanResult() {
  if (!latestResult || selectionModeToggle.checked) {
    return false;
  }

  return resultArtboard.scrollWidth > resultArtboard.clientWidth + 1
    || resultArtboard.scrollHeight > resultArtboard.clientHeight + 1;
}

function syncResultViewportState() {
  resultViewport.classList.toggle("is-pannable", canPanResult());
  resultViewport.classList.toggle("is-panning", panState.active);
}

function captureViewportAnchor(clientX = null, clientY = null) {
  if (!latestResult) {
    return null;
  }

  const artboardRect = resultArtboard.getBoundingClientRect();
  const shellRect = resultCanvasShell.getBoundingClientRect();
  const viewportOffsetX = clamp(
    clientX === null ? artboardRect.width / 2 : clientX - artboardRect.left,
    0,
    artboardRect.width,
  );
  const viewportOffsetY = clamp(
    clientY === null ? artboardRect.height / 2 : clientY - artboardRect.top,
    0,
    artboardRect.height,
  );
  const anchorClientX = clientX === null ? artboardRect.left + viewportOffsetX : clientX;
  const anchorClientY = clientY === null ? artboardRect.top + viewportOffsetY : clientY;
  const normalizedX = shellRect.width
    ? clamp((anchorClientX - shellRect.left) / shellRect.width, 0, 1)
    : 0.5;
  const normalizedY = shellRect.height
    ? clamp((anchorClientY - shellRect.top) / shellRect.height, 0, 1)
    : 0.5;

  return {
    normalizedX,
    normalizedY,
    viewportOffsetX,
    viewportOffsetY,
  };
}

function restoreViewportAnchor(anchor) {
  if (!anchor) {
    syncResultViewportState();
    return;
  }

  window.requestAnimationFrame(() => {
    const maxScrollLeft = Math.max(0, resultArtboard.scrollWidth - resultArtboard.clientWidth);
    const maxScrollTop = Math.max(0, resultArtboard.scrollHeight - resultArtboard.clientHeight);
    const targetLeft = resultCanvasShell.offsetLeft
      + (resultCanvasShell.offsetWidth * anchor.normalizedX)
      - anchor.viewportOffsetX;
    const targetTop = resultCanvasShell.offsetTop
      + (resultCanvasShell.offsetHeight * anchor.normalizedY)
      - anchor.viewportOffsetY;

    resultArtboard.scrollLeft = clamp(targetLeft, 0, maxScrollLeft);
    resultArtboard.scrollTop = clamp(targetTop, 0, maxScrollTop);
    syncResultViewportState();
  });
}

function updateZoomButtons() {
  for (const button of zoomButtons) {
    button.classList.remove("is-active");

    if (button.dataset.zoomAction === "fit" && lastZoomMode === "fit") {
      button.classList.add("is-active");
      continue;
    }

    if (button.dataset.zoom && Number(button.dataset.zoom) === Number(zoomRange.value) && lastZoomMode !== "fit") {
      button.classList.add("is-active");
    }
  }
}

function clipSelectionRect(rect) {
  if (!latestResult || !rect) {
    return null;
  }

  const x = clamp(rect.x, 0, latestResult.width - 1);
  const y = clamp(rect.y, 0, latestResult.height - 1);
  const width = clamp(rect.width, 1, latestResult.width - x);
  const height = clamp(rect.height, 1, latestResult.height - y);

  return { x, y, width, height };
}

function updateSelectionSummary() {
  if (!latestResult) {
    selectionSummary.textContent = "当前范围：整张结果。生成像素图后，默认下载已是透明背景；你也可以在右侧预览中框选局部微调。";
    return;
  }

  if (!selectionModeToggle.checked) {
    selectionSummary.textContent = "当前范围：整张结果。默认下载已是透明背景；未开启框选时，这里只会对整张结果做额外的局部去背微调。";
    return;
  }

  if (!selectionState.rect) {
    selectionSummary.textContent = "已开启框选。请在右侧像素结果预览上拖拽一个区域，只对该区域内与边缘相连的白色背景做额外微调。";
    return;
  }

  const rect = selectionState.rect;
  selectionSummary.textContent = `当前框选：x ${rect.x}, y ${rect.y}, 宽 ${rect.width}, 高 ${rect.height}。透明导出只会处理这个区域内与边缘相连的近白色背景。`;
}

function syncTransparentControls() {
  const hasResult = Boolean(latestResult);
  const selectionEnabled = selectionModeToggle.checked && hasResult;

  selectionModeChip.classList.toggle("is-active", Boolean(selectionModeToggle.checked));
  selectionOverlay.classList.toggle("is-enabled", selectionEnabled);
  clearSelectionButton.disabled = !selectionState.rect;
  downloadTransparentButton.hidden = !selectionEnabled;
  downloadTransparentButton.disabled = !selectionState.rect;
  updateSelectionSummary();
}

function renderSelectionBox() {
  if (!latestResult || !selectionModeToggle.checked || !selectionState.rect) {
    selectionBox.classList.remove("is-visible");
    syncTransparentControls();
    return;
  }

  const canvasRect = resultCanvas.getBoundingClientRect();
  if (!canvasRect.width || !canvasRect.height) {
    selectionBox.classList.remove("is-visible");
    syncTransparentControls();
    return;
  }

  const scaleX = canvasRect.width / latestResult.width;
  const scaleY = canvasRect.height / latestResult.height;
  const rect = selectionState.rect;

  selectionBox.style.left = `${rect.x * scaleX}px`;
  selectionBox.style.top = `${rect.y * scaleY}px`;
  selectionBox.style.width = `${Math.max(1, rect.width * scaleX)}px`;
  selectionBox.style.height = `${Math.max(1, rect.height * scaleY)}px`;
  selectionBox.classList.add("is-visible");
  syncTransparentControls();
}

function clearSelection() {
  selectionState.rect = null;
  selectionState.active = false;
  selectionState.start = null;
  selectionState.pointerId = null;
  renderSelectionBox();
}

function toImagePoint(event) {
  if (!latestResult) {
    return null;
  }

  const rect = resultCanvas.getBoundingClientRect();
  if (!rect.width || !rect.height) {
    return null;
  }

  const x = clamp(
    Math.floor(((event.clientX - rect.left) / rect.width) * latestResult.width),
    0,
    latestResult.width - 1,
  );
  const y = clamp(
    Math.floor(((event.clientY - rect.top) / rect.height) * latestResult.height),
    0,
    latestResult.height - 1,
  );

  return { x, y };
}

function beginSelection(event) {
  if (!selectionModeToggle.checked || !latestResult) {
    return;
  }

  const point = toImagePoint(event);
  if (!point) {
    return;
  }

  selectionState.active = true;
  selectionState.pointerId = event.pointerId;
  selectionState.start = point;
  selectionState.rect = { x: point.x, y: point.y, width: 1, height: 1 };
  selectionOverlay.setPointerCapture(event.pointerId);
  renderSelectionBox();
}

function updateSelection(event) {
  if (!selectionState.active || !selectionState.start || !latestResult) {
    return;
  }

  const point = toImagePoint(event);
  if (!point) {
    return;
  }

  const startX = Math.min(selectionState.start.x, point.x);
  const startY = Math.min(selectionState.start.y, point.y);
  const endX = Math.max(selectionState.start.x, point.x);
  const endY = Math.max(selectionState.start.y, point.y);

  selectionState.rect = clipSelectionRect({
    x: startX,
    y: startY,
    width: endX - startX + 1,
    height: endY - startY + 1,
  });

  renderSelectionBox();
}

function finishSelection(event) {
  if (!selectionState.active) {
    return;
  }

  selectionState.active = false;
  selectionState.start = null;

  if (selectionState.pointerId !== null && selectionOverlay.hasPointerCapture(selectionState.pointerId)) {
    selectionOverlay.releasePointerCapture(selectionState.pointerId);
  }

  selectionState.pointerId = null;
  updateSelection(event);
}

function beginPan(event) {
  if (!latestResult || selectionModeToggle.checked || event.button !== 0 || !canPanResult()) {
    return;
  }

  panState.active = true;
  panState.pointerId = event.pointerId;
  panState.startX = event.clientX;
  panState.startY = event.clientY;
  panState.scrollLeft = resultArtboard.scrollLeft;
  panState.scrollTop = resultArtboard.scrollTop;
  resultArtboard.setPointerCapture(event.pointerId);
  syncResultViewportState();
  event.preventDefault();
}

function updatePan(event) {
  if (!panState.active) {
    return;
  }

  const deltaX = event.clientX - panState.startX;
  const deltaY = event.clientY - panState.startY;

  resultArtboard.scrollLeft = panState.scrollLeft - deltaX;
  resultArtboard.scrollTop = panState.scrollTop - deltaY;
}

function finishPan(event) {
  if (!panState.active) {
    return;
  }

  if (
    panState.pointerId !== null
    && event
    && resultArtboard.hasPointerCapture(panState.pointerId)
  ) {
    resultArtboard.releasePointerCapture(panState.pointerId);
  }

  panState.active = false;
  panState.pointerId = null;
  syncResultViewportState();
}

function handleResultWheel(event) {
  if (!latestResult || selectionModeToggle.checked) {
    return;
  }

  if (event.deltaY === 0) {
    return;
  }

  event.preventDefault();
  const direction = event.deltaY < 0 ? 1 : -1;
  const baseZoom = lastZoomMode === "fit"
    ? currentRenderScale
    : Number(zoomRange.value) || currentRenderScale;
  const nextZoom = clampZoom(baseZoom + direction);

  if (nextZoom === Number(zoomRange.value) && lastZoomMode !== "fit") {
    return;
  }

  const anchor = captureViewportAnchor(event.clientX, event.clientY);
  setZoom(nextZoom, "manual", anchor);
}

function redrawResult(anchor = null) {
  syncZoomRangeVisual();

  if (!latestResult) {
    resultViewport.classList.remove("is-ready");
    resultArtboard.classList.remove("is-ready");
    drawEmptyCanvas("生成后的像素图会显示在这里。");
    zoomValue.textContent = `${zoomRange.value}x`;
    resultZoomMeta.textContent = `当前 ${zoomRange.value}x`;
    currentRenderScale = Number(zoomRange.value);
    clearSelection();
    updateZoomButtons();
    syncTransparentControls();
    syncResultViewportState();
    return;
  }

  if (lastZoomMode === "fit") {
    const fitMetrics = getResultFitMetrics();
    resultArtboard.style.minHeight = `${Math.round(fitMetrics.stageMinHeight)}px`;
    currentRenderScale = Math.max(
      0.1,
      Math.min(
        fitMetrics.availableWidth / latestResult.width,
        fitMetrics.availableHeight / latestResult.height,
      ),
    );
    zoomRange.value = String(clampZoom(currentRenderScale));
    syncZoomRangeVisual();
  } else {
    resultArtboard.style.removeProperty("min-height");
  }

  const zoom = Math.max(0.1, currentRenderScale || Number(zoomRange.value) || 1);
  const displayWidth = latestResult.width * zoom;
  const displayHeight = latestResult.height * zoom;

  resultViewport.classList.add("is-ready");
  resultArtboard.classList.add("is-ready");
  zoomValue.textContent = `${formatScale(zoom)}x`;
  resultZoomMeta.textContent = lastZoomMode === "fit"
    ? `适应 ${formatScale(zoom)}x`
    : `当前 ${formatScale(zoom)}x`;
  resultCanvas.width = latestResult.width;
  resultCanvas.height = latestResult.height;
  resultCanvas.style.width = `${displayWidth}px`;
  resultCanvas.style.height = `${displayHeight}px`;
  resultCanvasShell.style.width = `${displayWidth}px`;
  resultCanvasShell.style.height = `${displayHeight}px`;
  resultContext.imageSmoothingEnabled = false;
  resultContext.clearRect(0, 0, latestResult.width, latestResult.height);
  resultContext.drawImage(latestResult.image, 0, 0, latestResult.width, latestResult.height);

  renderSelectionBox();
  updateZoomButtons();

  if (lastZoomMode === "fit") {
    centerResultViewport();
    return;
  }

  restoreViewportAnchor(anchor);
}

function setZoom(value, mode = "manual", anchor = null) {
  const viewportAnchor = anchor || (latestResult && mode !== "fit" ? captureViewportAnchor() : null);
  currentRenderScale = Math.max(0.1, Number(value) || 1);
  zoomRange.value = String(clampZoom(currentRenderScale));
  lastZoomMode = mode;
  redrawResult(viewportAnchor);
}

function fitZoomToViewport() {
  if (!latestResult) {
    return;
  }

  const fitMetrics = getResultFitMetrics();
  const fittedZoom = Math.max(
    0.1,
    Math.min(
      fitMetrics.availableWidth / latestResult.width,
      fitMetrics.availableHeight / latestResult.height,
    ),
  );

  setZoom(fittedZoom, "fit");
}

async function loadResultImage(data) {
  const image = new Image();
  image.decoding = "async";

  await new Promise((resolve, reject) => {
    image.onload = resolve;
    image.onerror = reject;
    image.src = data.image_url;
  });

  latestResult = {
    image,
    width: data.width,
    height: data.height,
    filename: data.filename,
    colors: data.colors,
    hasTransparentBackground: Boolean(data.has_transparent_background),
    backgroundTransparencyEnabled: Boolean(data.background_transparency_enabled),
    contentCropApplied: Boolean(data.content_crop_applied),
    contentBounds: data.content_bounds || null,
    transparencyMode: data.transparency_mode || "auto-background",
  };

  revokeTransparentDownloadUrl();
  clearSelection();
  resultMeta.textContent = `${data.width} x ${data.height} · ${data.colors} 色`;
  resultHint.textContent = data.has_transparent_background
    ? "背景已自动透明化，可以直接下载 PNG，也会自动进入图片剪接环节。"
    : "可以切换倍率，也可以点“适应窗口”快速回到完整视图。";
  downloadLink.href = data.image_url;
  downloadLink.download = data.filename;
  downloadLink.classList.remove("disabled");
  resultHint.textContent = buildResultHintText(data);
  downloadHint.textContent = data.has_transparent_background
    ? `已生成 ${data.filename}（透明 PNG）`
    : `已生成 ${data.filename}`;
  downloadHint.textContent = data.has_transparent_background
    ? `已生成 ${data.filename}（透明背景）`
    : `已生成 ${data.filename}`;

  resultHint.textContent = buildResultHintText(data);
  downloadHint.textContent = data.has_transparent_background
    ? `已生成 ${data.filename}（透明 PNG）`
    : `已生成 ${data.filename}`;
  fitZoomToViewport();
  syncTransparentControls();
  syncQuickResultDownload();
  syncQuickFlowPreviews();
}

async function loadResultImage(data) {
  const image = new Image();
  image.decoding = "async";

  await new Promise((resolve, reject) => {
    image.onload = resolve;
    image.onerror = reject;
    image.src = data.image_url;
  });

  latestResult = {
    image,
    width: data.width,
    height: data.height,
    filename: data.filename,
    colors: data.colors,
    hasTransparentBackground: Boolean(data.has_transparent_background),
    backgroundTransparencyEnabled: Boolean(data.background_transparency_enabled),
    contentCropApplied: Boolean(data.content_crop_applied),
    contentBounds: data.content_bounds || null,
    transparencyMode: data.transparency_mode || "auto-background",
  };

  revokeTransparentDownloadUrl();
  clearSelection();
  resultMeta.textContent = `${data.width} x ${data.height} / ${data.colors} colors`;
  resultHint.textContent = buildResultHintText(data);
  downloadLink.href = data.image_url;
  downloadLink.download = data.filename;
  downloadLink.classList.remove("disabled");
  downloadHint.textContent = data.has_transparent_background
    ? `已生成 ${data.filename}（透明 PNG）`
    : `已生成 ${data.filename}`;

  fitZoomToViewport();
  syncTransparentControls();
  syncQuickResultDownload();
  syncQuickFlowPreviews();
}

function requestPixelize(formData) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.open("POST", "/api/pixelize");
    xhr.responseType = "json";

    xhr.upload.addEventListener("progress", (event) => {
      if (!event.lengthComputable) {
        return;
      }

      const percent = Math.max(4, Math.round((event.loaded / event.total) * 100));
      setUploadProgress({
        visible: true,
        value: percent,
        label: `正在上传图片 ${percent}%`,
        state: "uploading",
      });
      setStatus("正在上传图片...", "loading", "文件已开始上传，请稍等。");
    });

    xhr.upload.addEventListener("load", () => {
      setUploadProgress({
        visible: true,
        value: 100,
        label: "上传完成，正在生成像素图...",
        state: "uploading",
        indeterminate: true,
      });
      setStatus("正在生成像素图...", "loading", "图片已上传，正在进行像素化处理。");
    });

    xhr.onerror = () => {
      reject(new Error("网络连接异常，请稍后再试。"));
    };

    xhr.onload = () => {
      const payload = xhr.response || safeJsonParse(xhr.responseText) || {};

      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(payload);
        return;
      }

      reject(new Error(payload.error || "生成失败，请稍后再试。"));
    };

    xhr.send(formData);
  });
}

function buildPixelizeFormData() {
  const formData = new FormData(form);
  const file = getCurrentUploadFile();

  if (file) {
    formData.set("image", file, file.name);
  }

  formData.set("fit_content_bounds", contentBoundsCompressionEnabled() ? "1" : "0");
  formData.set("transparent_background", backgroundTransparencyEnabled() ? "1" : "0");

  return formData;
}

async function handoffGeneratedResultToAtlas(payload, options = {}) {
  const atlasBridge = window.perfectPixelAtlasBridge;
  if (!atlasBridge || typeof atlasBridge.runFromGeneratedResult !== "function") {
    return false;
  }

  await atlasBridge.runFromGeneratedResult({
    dataUrl: payload.image_url,
    filename: payload.filename,
    scrollIntoView: options.scrollIntoView !== false,
  });
  return true;
}

function resetGeneratedWorkflow(options = {}) {
  const { clearAtlas = true } = options;
  const atlasBridge = window.perfectPixelAtlasBridge;

  latestResult = null;
  revokeTransparentDownloadUrl();
  clearSelection();
  lastZoomMode = "fit";
  resultMeta.textContent = "等待生成";
  resultHint.textContent = "重新上传图片后，请先重新生成像素图。";
  downloadLink.classList.add("disabled");
  downloadLink.href = "#";
  downloadLink.removeAttribute("download");
  downloadHint.textContent = "生成完成后会自动启用下载按钮。";
  setUploadProgress();
  redrawResult();
  syncQuickResultDownload();
  syncQuickFlowPreviews();

  if (clearAtlas && atlasBridge && typeof atlasBridge.resetForNewSource === "function") {
    atlasBridge.resetForNewSource();
  }
}

async function handleGenerate(event) {
  event.preventDefault();
  const preserveViewport = preserveQuickViewportForNextGenerate;
  preserveQuickViewportForNextGenerate = false;

  const file = getCurrentUploadFile();
  if (!file) {
    setStatus("请先选择一张图片", "error", "上传图片后才能开始生成像素图。");
    return;
  }

  setPreviewBusy(true);
  generateButton.disabled = true;
  resultMeta.textContent = "生成中...";
  resultHint.textContent = "正在处理图片，请稍等。";
  downloadLink.classList.add("disabled");
  downloadLink.removeAttribute("href");
  downloadLink.removeAttribute("download");
  syncQuickResultDownload();
  downloadHint.textContent = "生成完成后会自动启用下载按钮。";
  setUploadProgress({
    visible: true,
    value: 4,
    label: "正在准备上传...",
    state: "uploading",
  });
  setStatus("正在上传图片...", "loading", "文件正在发送到本地生成接口。");

  try {
    const payload = await requestPixelize(buildPixelizeFormData());

    await loadResultImage(payload);
    setStatus("像素生成完成，正在自动剪接...", "loading", "背景已自动透明化，正在把结果送入图片剪接流程。");
    resultHint.textContent = "背景已自动透明化，系统正在自动进入图片剪接环节。";

    setStatus("像素生成完成，正在同步到切片区...", "loading", "系统正在把结果送入图片切片流程。");
    resultHint.textContent = buildResultHintText(payload);

    let atlasWorkflowPrepared = false;
    try {
      atlasWorkflowPrepared = await handoffGeneratedResultToAtlas(payload, {
        scrollIntoView: !preserveViewport,
      });
    } catch (atlasError) {
      atlasWorkflowPrepared = false;
      setStatus(
        "像素生成完成，自动剪接失败",
        "error",
        "像素图已经生成，背景也已透明化，但自动剪接没有完成，可在下方切片模块手动重试。",
      );
    }

    if (atlasWorkflowPrepared) {
      setStatus("生成完成，等待你开始图集切片", "success", "像素结果已经送入下方图集模块。请选择导出类型后，再开始自动识别切片。");
      resultHint.textContent = "背景已自动透明化。结果图已送到下方图集模块，选择导出类型后再开始切片。";
    }

    resultHint.textContent = buildResultHintText(payload);

    const atlasWorkflowStarted = false;
    setUploadProgress({
      visible: true,
      value: 100,
      label: atlasWorkflowStarted ? "生成与自动剪接完成。" : "生成完成，可下载 PNG。",
      state: "success",
    });
    if (atlasWorkflowStarted) {
      setStatus("生成与剪接完成", "success", "像素结果已经生成、背景已自动透明，并已自动进入图片剪接环节。");
      resultHint.textContent = "背景已自动透明化，切片结果也已在下方生成。";
    } else if (!atlasWorkflowPrepared && !statusText.classList.contains("status-error")) {
      setStatus("生成完成", "success", "像素结果已经生成，背景已自动透明化，可以继续预览或下载 PNG。");
    }
  } catch (error) {
    latestResult = null;
    redrawResult();
    syncQuickResultDownload();
    resultMeta.textContent = "生成失败";
    resultHint.textContent = "请调整尺寸或更换图片后再试一次。";
    setUploadProgress({
      visible: true,
      value: 100,
      label: error.message || "生成失败，请稍后再试。",
      state: "error",
    });
    setStatus(error.message || "生成失败，请稍后再试。", "error", "请调整参数或更换图片后重新生成。");
  } finally {
    setPreviewBusy(false);
    generateButton.disabled = false;
    syncTransparentControls();
  }
}

function isImageFile(file) {
  if (!file) {
    return false;
  }

  return file.type.startsWith("image/") || /\.(png|jpe?g|webp)$/i.test(file.name);
}

function assignImageFile(file) {
  resetGeneratedWorkflow();
  currentUploadFile = file;
  trySyncNativeInput(file);
  updateSourcePreview();
}

function preventDragDefaults(event) {
  event.preventDefault();
  event.stopPropagation();
}

function getExportRegion() {
  if (!latestResult) {
    return null;
  }

  if (selectionModeToggle.checked) {
    return selectionState.rect;
  }

  return {
    x: 0,
    y: 0,
    width: latestResult.width,
    height: latestResult.height,
  };
}

function isNearWhite(data, pixelIndex, threshold) {
  const offset = pixelIndex * 4;
  return data[offset] >= threshold
    && data[offset + 1] >= threshold
    && data[offset + 2] >= threshold
    && data[offset + 3] > 0;
}

function applyConnectedWhiteTransparency(imageData, region, threshold) {
  if (!region) {
    return;
  }

  const { data, width } = imageData;
  const rect = clipSelectionRect(region);
  if (!rect) {
    return;
  }

  const left = rect.x;
  const top = rect.y;
  const right = rect.x + rect.width - 1;
  const bottom = rect.y + rect.height - 1;
  const visited = new Uint8Array(width * imageData.height);
  const queue = [];
  let head = 0;

  function enqueue(x, y) {
    const index = y * width + x;
    if (visited[index]) {
      return;
    }

    visited[index] = 1;
    if (!isNearWhite(data, index, threshold)) {
      return;
    }

    queue.push([x, y]);
  }

  for (let x = left; x <= right; x += 1) {
    enqueue(x, top);
    enqueue(x, bottom);
  }

  for (let y = top; y <= bottom; y += 1) {
    enqueue(left, y);
    enqueue(right, y);
  }

  while (head < queue.length) {
    const [x, y] = queue[head];
    head += 1;

    const index = y * width + x;
    const offset = index * 4;
    data[offset + 3] = 0;

    if (x > left) {
      enqueue(x - 1, y);
    }
    if (x < right) {
      enqueue(x + 1, y);
    }
    if (y > top) {
      enqueue(x, y - 1);
    }
    if (y < bottom) {
      enqueue(x, y + 1);
    }
  }
}

function blobFromCanvas(canvas) {
  return new Promise((resolve) => {
    canvas.toBlob((blob) => resolve(blob), "image/png");
  });
}

function buildTransparentFilename(filename) {
  const stem = filename.replace(/\.png$/i, "");
  return `${stem}-transparent.png`;
}

function triggerDownloadFromBlob(blob, filename) {
  if (!blob) {
    throw new Error("透明 PNG 生成失败，请稍后再试。");
  }

  revokeTransparentDownloadUrl();
  transparentDownloadUrl = URL.createObjectURL(blob);

  const link = document.createElement("a");
  link.href = transparentDownloadUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
}

async function handleTransparentDownload() {
  if (!latestResult) {
    setStatus("请先生成像素图", "error", "生成结果后才能导出透明 PNG。");
    return;
  }

  const region = getExportRegion();
  if (selectionModeToggle.checked && !region) {
    setStatus("请先框选一个区域", "error", "开启框选模式后，需要先在右侧像素结果上拖出范围。");
    return;
  }

  const threshold = Number(alphaThreshold.value);
  const offscreenCanvas = document.createElement("canvas");
  offscreenCanvas.width = latestResult.width;
  offscreenCanvas.height = latestResult.height;
  const context = offscreenCanvas.getContext("2d");

  context.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
  context.drawImage(latestResult.image, 0, 0, offscreenCanvas.width, offscreenCanvas.height);

  const imageData = context.getImageData(0, 0, offscreenCanvas.width, offscreenCanvas.height);
  applyConnectedWhiteTransparency(imageData, region, threshold);
  context.putImageData(imageData, 0, 0);

  const filename = buildTransparentFilename(latestResult.filename);
  const blob = await blobFromCanvas(offscreenCanvas);
  triggerDownloadFromBlob(blob, filename);

  setStatus("透明 PNG 已导出", "success", "去背结果已经下载，可直接用于贴图或材质叠加。");
  downloadHint.textContent = `已导出 ${filename}`;
}

for (const input of sizeModeInputs) {
  input.addEventListener("change", toggleManualInputs);
}

if (quickStartPanel) {
  quickSelectImageButton.addEventListener("click", () => {
    quickImageInput.click();
  });

  quickUploadDropzone.addEventListener("click", (event) => {
    if (event.target === quickSelectImageButton) {
      return;
    }

    quickImageInput.click();
  });

  quickUploadDropzone.addEventListener("keydown", (event) => {
    if (event.key !== "Enter" && event.key !== " ") {
      return;
    }

    event.preventDefault();
    quickImageInput.click();
  });

  quickImageInput.addEventListener("click", () => {
    quickImageInput.value = "";
  });

  quickImageInput.addEventListener("change", () => {
    const file = quickImageInput.files && quickImageInput.files[0];
    if (file) {
      assignImageFile(file);
      syncQuickStartFromMain();
    }
  });

  quickUploadDropzone.addEventListener("dragenter", (event) => {
    preventDragDefaults(event);
    quickDragDepth += 1;
    quickUploadDropzone.classList.add("is-dragover");
    quickUploadName.textContent = "松开即可上传图片";
    quickUploadMeta.textContent = "支持 PNG / JPG / JPEG / WebP";
  });

  quickUploadDropzone.addEventListener("dragover", (event) => {
    preventDragDefaults(event);
    quickUploadDropzone.classList.add("is-dragover");
  });

  quickUploadDropzone.addEventListener("dragleave", (event) => {
    preventDragDefaults(event);
    quickDragDepth = Math.max(0, quickDragDepth - 1);

    if (quickDragDepth === 0) {
      quickUploadDropzone.classList.remove("is-dragover");
      syncQuickUploadSummary();
    }
  });

  quickUploadDropzone.addEventListener("dragend", (event) => {
    preventDragDefaults(event);
    quickDragDepth = 0;
    quickUploadDropzone.classList.remove("is-dragover");
    syncQuickUploadSummary();
  });

  quickUploadDropzone.addEventListener("drop", (event) => {
    preventDragDefaults(event);
    quickDragDepth = 0;
    quickUploadDropzone.classList.remove("is-dragover");

    const droppedFiles = event.dataTransfer && event.dataTransfer.files
      ? Array.from(event.dataTransfer.files)
      : [];
    const file = droppedFiles.find(isImageFile);

    if (!file) {
      syncQuickUploadSummary();
      return;
    }

    assignImageFile(file);
    syncQuickStartFromMain();
  });

  for (const input of quickSizeModeInputs) {
    input.addEventListener("change", () => {
      setMainSizeMode(input.value);
      syncQuickSizeControls();
    });
  }

  quickWidthInput.addEventListener("input", () => {
    syncQuickValueToMain(quickWidthInput, widthInput);
    syncQuickSizeControls();
  });

  quickHeightInput.addEventListener("input", () => {
    syncQuickValueToMain(quickHeightInput, heightInput);
    syncQuickSizeControls();
  });

  if (quickFitContentBoundsToggle) {
    quickFitContentBoundsToggle.addEventListener("change", () => {
      syncQuickCheckedToMain(quickFitContentBoundsToggle, fitContentBoundsToggle);
      syncQuickSizeControls();
    });
  }

  if (quickSampleMethod && sampleMethod) {
    quickSampleMethod.addEventListener("change", () => {
      sampleMethod.value = quickSampleMethod.value;
      syncQuickGenerateControls();
    });
  }

  if (quickRefineInput && refineInput) {
    quickRefineInput.addEventListener("input", () => {
      refineInput.value = quickRefineInput.value;
      syncQuickGenerateControls();
    });
  }

  if (quickTransparentBackgroundToggle) {
    quickTransparentBackgroundToggle.addEventListener("change", () => {
      syncQuickCheckedToMain(quickTransparentBackgroundToggle, transparentBackgroundToggle);
      syncQuickGenerateControls();
    });
  }

  for (const button of quickSizePresetButtons) {
    button.addEventListener("click", () => {
      setMainSizeMode("manual");
      quickWidthInput.value = button.dataset.width || quickWidthInput.value;
      quickHeightInput.value = button.dataset.height || quickHeightInput.value;
      syncQuickValueToMain(quickWidthInput, widthInput);
      syncQuickValueToMain(quickHeightInput, heightInput);
      syncQuickSizeControls();
    });
  }

  quickGenerateButton.addEventListener("click", () => {
    if (!generateButton.disabled) {
      preserveQuickViewportForNextGenerate = true;
      form.requestSubmit();
      window.setTimeout(() => {
        preserveQuickViewportForNextGenerate = false;
      }, 0);
    }
  });

  for (const input of quickExportModeInputs) {
    input.addEventListener("change", () => {
      setAtlasExportMode(input.value);
      syncQuickExportControls();
    });
  }

  quickPixelWidthInput.addEventListener("input", () => {
    syncQuickValueToMain(quickPixelWidthInput, atlasPixelOutputWidth);
    syncQuickExportControls();
  });

  quickPixelHeightInput.addEventListener("input", () => {
    syncQuickValueToMain(quickPixelHeightInput, atlasPixelOutputHeight);
    syncQuickExportControls();
  });

  for (const button of quickSlicePresetButtons) {
    button.addEventListener("click", () => {
      applyLinkedQuickSliceSize(button.dataset.size || "");
    });
  }

  quickSlicePixelButton.addEventListener("click", () => {
    if (!sliceAtlasButton || sliceAtlasButton.disabled || !atlasForm) {
      return;
    }

    setAtlasExportMode("pixel");
    atlasForm.requestSubmit();
  });

  quickSliceButton.addEventListener("click", () => {
    if (!sliceAtlasButton || sliceAtlasButton.disabled || !atlasForm) {
      return;
    }

    atlasForm.requestSubmit();
  });

  const mainExportModeInputs = document.querySelectorAll('input[name="export_mode"]');
  for (const input of mainExportModeInputs) {
    input.addEventListener("change", syncQuickExportControls);
  }

  if (atlasPixelOutputWidth) {
    atlasPixelOutputWidth.addEventListener("input", syncQuickExportControls);
  }

  if (atlasPixelOutputHeight) {
    atlasPixelOutputHeight.addEventListener("input", syncQuickExportControls);
  }
}

window.addEventListener("perfectpixel:quick-sync", () => {
  if (quickStartPanel) {
    syncQuickStartFromMain();
  }
});

for (const button of presetButtons) {
  button.addEventListener("click", () => {
    document.querySelector('input[name="size_mode"][value="manual"]').checked = true;
    widthInput.value = button.dataset.width;
    heightInput.value = button.dataset.height;
    toggleManualInputs();
  });
}

for (const button of zoomButtons) {
  button.addEventListener("click", () => {
    if (button.dataset.zoomAction === "fit") {
      fitZoomToViewport();
      return;
    }

    if (button.dataset.zoom) {
      setZoom(Number(button.dataset.zoom), "manual");
    }
  });
}

uploadDropzone.addEventListener("click", (event) => {
  if (event.target === imageInput) {
    return;
  }

  event.preventDefault();
  imageInput.click();
});

imageInput.addEventListener("click", () => {
  imageInput.value = "";
});

uploadDropzone.addEventListener("dragenter", (event) => {
  preventDragDefaults(event);
  dragDepth += 1;
  uploadDropzone.classList.add("is-dragover");
  uploadFilename.textContent = "松开即可上传这张图片";
  uploadFileMeta.textContent = "支持 PNG、JPG、JPEG、WebP";
});

uploadDropzone.addEventListener("dragover", (event) => {
  preventDragDefaults(event);
  uploadDropzone.classList.add("is-dragover");
  uploadFilename.textContent = "松开即可上传这张图片";
  uploadFileMeta.textContent = "支持 PNG、JPG、JPEG、WebP";
});

uploadDropzone.addEventListener("dragleave", (event) => {
  preventDragDefaults(event);
  dragDepth = Math.max(0, dragDepth - 1);

  if (dragDepth === 0) {
    uploadDropzone.classList.remove("is-dragover");
    resetDropzoneHint();
  }
});

uploadDropzone.addEventListener("dragend", (event) => {
  preventDragDefaults(event);
  dragDepth = 0;
  uploadDropzone.classList.remove("is-dragover");
  resetDropzoneHint();
});

uploadDropzone.addEventListener("drop", (event) => {
  preventDragDefaults(event);
  dragDepth = 0;
  uploadDropzone.classList.remove("is-dragover");

  const droppedFiles = event.dataTransfer && event.dataTransfer.files
    ? Array.from(event.dataTransfer.files)
    : [];
  const file = droppedFiles.find(isImageFile);

  if (!file) {
    uploadFilename.textContent = "当前还没有选择图片";
    uploadFileMeta.textContent = "请拖入 PNG、JPG、JPEG、WebP";
    setStatus("请拖入正确的图片格式", "error", "仅支持 PNG、JPG、JPEG、WebP 图片。");
    return;
  }

  assignImageFile(file);
  setStatus("图片已载入", "idle", "拖拽上传成功，现在可以开始生成。");
});

window.addEventListener("dragover", (event) => {
  if (
    !(event.target instanceof Element)
    || !event.target.closest("#uploadDropzone, #atlasUploadDropzone")
  ) {
    event.preventDefault();
  }
});

window.addEventListener("drop", (event) => {
  if (
    !(event.target instanceof Element)
    || !event.target.closest("#uploadDropzone, #atlasUploadDropzone")
  ) {
    event.preventDefault();
  }
});

imageInput.addEventListener("change", () => {
  const file = imageInput.files && imageInput.files[0];
  if (file) {
    assignImageFile(file);
    return;
  }

  currentUploadFile = null;
  resetGeneratedWorkflow();
  updateSourcePreview();
});

widthInput.addEventListener("input", () => {
  updateSizeSummary();
  syncPresetButtons();
  syncQuickSizeControls();
});

heightInput.addEventListener("input", () => {
  updateSizeSummary();
  syncPresetButtons();
  syncQuickSizeControls();
});

if (fitContentBoundsToggle) {
  fitContentBoundsToggle.addEventListener("change", () => {
    syncToggleChipState(fitContentBoundsChip, fitContentBoundsToggle);
    updateSizeSummary();
    syncQuickSizeControls();
  });
}

if (sampleMethod) {
  sampleMethod.addEventListener("change", syncQuickGenerateControls);
}

if (refineInput) {
  refineInput.addEventListener("input", syncQuickGenerateControls);
}

if (transparentBackgroundToggle) {
  transparentBackgroundToggle.addEventListener("change", () => {
    syncToggleChipState(transparentBackgroundChip, transparentBackgroundToggle);
    if (generationSummary) {
      generationSummary.textContent = buildGenerationSummaryText();
    }
    syncQuickGenerateControls();
  });
}

zoomRange.addEventListener("input", () => setZoom(Number(zoomRange.value), "manual"));

selectionModeToggle.addEventListener("change", () => {
  renderSelectionBox();
  syncTransparentControls();
});

clearSelectionButton.addEventListener("click", () => {
  clearSelection();
  syncTransparentControls();
});

alphaThreshold.addEventListener("input", () => {
  alphaThresholdValue.textContent = alphaThreshold.value;
});

downloadTransparentButton.addEventListener("click", handleTransparentDownload);
form.addEventListener("submit", handleGenerate);

selectionOverlay.addEventListener("pointerdown", (event) => {
  beginSelection(event);
});

selectionOverlay.addEventListener("pointermove", (event) => {
  updateSelection(event);
});

selectionOverlay.addEventListener("pointerup", (event) => {
  finishSelection(event);
});

selectionOverlay.addEventListener("pointercancel", (event) => {
  finishSelection(event);
});

window.addEventListener("resize", scheduleResultViewportSync);

if (window.visualViewport) {
  window.visualViewport.addEventListener("resize", scheduleResultViewportSync);
}

if (window.ResizeObserver && resultPanel) {
  let observedResultPanelWidth = Math.round(resultPanel.getBoundingClientRect().width);
  const resultPanelObserver = new ResizeObserver((entries) => {
    const entry = entries[0];
    const nextWidth = Math.round(entry.contentRect.width);

    if (nextWidth === observedResultPanelWidth) {
      return;
    }

    observedResultPanelWidth = nextWidth;
    scheduleResultViewportSync();
  });

  resultPanelObserver.observe(resultPanel);
}

toggleManualInputs();
alphaThresholdValue.textContent = alphaThreshold.value;
updateSourcePreview();
drawEmptyCanvas("生成后的像素图会显示在这里。");
syncZoomRangeVisual();
updateZoomButtons();
syncTransparentControls();
syncQuickStartFromMain();
