import type { CanvasState } from "@dada/shared-contracts";

import { renderCanvasStateForExport } from "./editor-stage.js";
import { P0A_TEXT_TEMPLATES, TextEditSession } from "./text-assets.js";
import type { ArchivedFontStatus } from "./text-font-loader.js";

export type ExportFormat = "jpg" | "png";
export type ExportRatio = CanvasState["ratio"];

export const EXPORT_DIMENSIONS: Readonly<Record<ExportRatio, { height: number; width: number }>> = {
  "1:1": { height: 1080, width: 1080 },
  "3:4": { height: 1440, width: 1080 },
  "4:3": { height: 1080, width: 1440 },
  "9:16": { height: 1920, width: 1080 },
};

export interface ExportSettings {
  format: ExportFormat;
  height: number;
  mimeType: "image/jpeg" | "image/png";
  quality?: number;
  width: number;
}

export function resolveExportSettings(input: { format: ExportFormat; quality?: number; ratio: ExportRatio }): ExportSettings {
  const dimensions = EXPORT_DIMENSIONS[input.ratio];
  if (input.format === "png") return { format: "png", height: dimensions.height, mimeType: "image/png", width: dimensions.width };
  const quality = input.quality ?? 92;
  if (!Number.isInteger(quality) || quality < 80 || quality > 100) throw new Error("export_quality_invalid");
  return { format: "jpg", height: dimensions.height, mimeType: "image/jpeg", quality, width: dimensions.width };
}

export function completePendingTextForExport(canvasState: CanvasState, draft: CanvasState["elements"][number]) {
  if (draft.type !== "text_template") throw new Error("export_pending_text_invalid");
  const index = canvasState.elements.findIndex((element) => element.element_id === draft.element_id && element.type === "text_template");
  if (index < 0) throw new Error("export_pending_text_missing");
  const complete = new TextEditSession(draft, P0A_TEXT_TEMPLATES).complete();
  const next = structuredClone(canvasState);
  next.elements[index] = complete;
  return next;
}

export async function sha256Blob(blob: Blob) {
  const digest = await crypto.subtle.digest("SHA-256", await blob.arrayBuffer());
  return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
}

export function encodeCanvasExport(canvas: HTMLCanvasElement, settings: ExportSettings) {
  if (canvas.width !== settings.width || canvas.height !== settings.height) throw new Error("export_canvas_dimensions_invalid");
  return new Promise<Blob>((resolve, reject) => {
    const quality = settings.format === "jpg" ? settings.quality! / 100 : undefined;
    canvas.toBlob((blob) => {
      if (!blob || blob.type !== settings.mimeType) reject(new Error("export_encode_failed"));
      else resolve(blob);
    }, settings.mimeType, quality);
  });
}

export async function composeCanvasExport(input: {
  canvasState: CanvasState;
  fontStatuses: Readonly<Record<string, ArchivedFontStatus>>;
  format: ExportFormat;
  projectId: string;
  quality?: number;
}) {
  const settings = resolveExportSettings({ format: input.format, ratio: input.canvasState.ratio, ...(input.quality === undefined ? {} : { quality: input.quality }) });
  if (input.canvasState.pixel_width !== settings.width || input.canvasState.pixel_height !== settings.height) throw new Error("export_canvas_dimensions_invalid");
  const canvas = await renderCanvasStateForExport({ canvasState: input.canvasState, fontStatuses: input.fontStatuses, projectId: input.projectId });
  return encodeCanvasExport(canvas, settings);
}

export async function downloadExportBlob(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob);
  try {
    const anchor = document.createElement("a");
    anchor.download = filename;
    anchor.href = url;
    anchor.style.display = "none";
    document.body.append(anchor);
    anchor.click();
    anchor.remove();
  } finally {
    setTimeout(() => URL.revokeObjectURL(url), 0);
  }
}

export function exportFilename(projectName: string, format: ExportFormat) {
  const safeName = projectName.trim().replace(/[<>:"/\\|?*\u0000-\u001F]/g, "_").slice(0, 80) || "Dada成品";
  return `${safeName}.${format === "jpg" ? "jpg" : "png"}`;
}

export async function saveLatestExport(input: {
  blob: Blob;
  csrfToken: string;
  format: ExportFormat;
  height: number;
  projectId: string;
  stateVersion: number;
  width: number;
}) {
  const sha256 = await sha256Blob(input.blob);
  const form = new FormData();
  form.append("format", input.format);
  form.append("export_id", crypto.randomUUID());
  form.append("sha256", sha256);
  form.append("byte_size", String(input.blob.size));
  form.append("pixel_width", String(input.width));
  form.append("pixel_height", String(input.height));
  form.append("state_version", String(input.stateVersion));
  form.append("export_file", input.blob, `latest.${input.format}`);
  const response = await fetch(`/api/v1/projects/${encodeURIComponent(input.projectId)}/latest-exports/${input.format}`, {
    body: form,
    credentials: "same-origin",
    headers: { "X-CSRF-Token": input.csrfToken },
    method: "PUT",
  });
  if (!response.ok) throw new Error(response.status === 409 ? "latest_export_conflict" : "latest_export_failed");
}
