export type ExitDraft = { key: string; mailbox: string; entries: Array<[string, string | File]>; createdAt: number };
export const COMPOSE_EXIT_ENDPOINT = "/api/mailbox/compose-exit";
export const COMPOSE_EXIT_NOTICE = "작성중인 메일이 임시메일함으로 저장되었습니다";
const DB_NAME = "official-mail-exit-drafts";
let database: Promise<IDBDatabase> | undefined;

export function openExitDraftDatabase() {
  database ??= new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, 1);
    request.onupgradeneeded = () => request.result.createObjectStore("drafts", { keyPath: "key" });
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => { database = undefined; reject(request.error); };
  });
  return database;
}

export async function storeExitDraft(draft: ExitDraft) {
  const db = await openExitDraftDatabase();
  await new Promise<void>((resolve, reject) => {
    const transaction = db.transaction("drafts", "readwrite");
    transaction.objectStore("drafts").put(draft);
    transaction.oncomplete = () => resolve();
    transaction.onerror = () => reject(transaction.error);
    transaction.onabort = () => reject(transaction.error);
  });
}

export async function removeExitDraft(key: string) {
  const db = await openExitDraftDatabase();
  await new Promise<void>((resolve, reject) => {
    const transaction = db.transaction("drafts", "readwrite");
    transaction.objectStore("drafts").delete(key);
    transaction.oncomplete = () => resolve();
    transaction.onerror = () => reject(transaction.error);
  });
}

export async function readExitDrafts(mailbox: string) {
  const db = await openExitDraftDatabase();
  return new Promise<ExitDraft[]>((resolve, reject) => {
    const request = db.transaction("drafts").objectStore("drafts").getAll();
    request.onsuccess = () => resolve((request.result as ExitDraft[]).filter(draft => draft.mailbox === mailbox));
    request.onerror = () => reject(request.error);
  });
}

export function exitDraftForm(draft: ExitDraft) {
  const form = new FormData();
  for (const [name, value] of draft.entries) form.append(name, value);
  form.set("exitKey", draft.key);
  return form;
}

export function composeDraftFingerprint(form: FormData) {
  return JSON.stringify(Array.from(form.entries(), ([name, value]) => [name, typeof value === "string" ? value : [value.name, value.size, value.lastModified]]));
}

export async function submitExitDraft(draft: ExitDraft) {
  // Preserve large attachments through the existing streaming storage path;
  // the compose body endpoint must not buffer hundreds of MiB of multipart.
  let regularBytes = 0;
  for (const [name, value] of [...draft.entries]) {
    if (name !== "attachmentFiles" || typeof value === "string") continue;
    if (regularBytes + value.size <= 40 * 1024 * 1024) { regularBytes += value.size; continue; }
    const endpoint = "/api/mailbox/uploads/large/direct";
    const start = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "start", contentType: value.type || "application/octet-stream", filename: value.name, sizeBytes: value.size }) });
    if (!start.ok) throw new Error(`draft-upload-${start.status}`);
    const session = await start.json() as { token: string; partBytes: number; parts: Array<{ partNumber: number; url: string }> };
    let completed = false;
    try {
      for (const part of session.parts) {
        const begin = (part.partNumber - 1) * session.partBytes;
        const response = await fetch(part.url, { method: "PUT", body: value.slice(begin, Math.min(value.size, begin + session.partBytes)) });
        if (!response.ok) throw new Error("draft-upload-failed");
      }
      const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "complete", token: session.token }) });
      if (!response.ok) throw new Error("draft-upload-failed");
      const result = await response.json() as { id: number };
      if (!Number.isSafeInteger(result.id) || result.id < 1) throw new Error("draft-upload-unconfirmed");
      completed = true;
      const previous = draft.entries.find(([field]) => field === "largeAttachmentUploadIds")?.[1];
      draft.entries = draft.entries.filter(([field, file]) => !(field === name && file === value) && field !== "largeAttachmentUploadIds");
      draft.entries.push(["largeAttachmentUploadIds", [previous, result.id].filter(Boolean).join(",")]);
      await storeExitDraft(draft).catch(() => undefined);
    } finally {
      if (!completed) await fetch(endpoint, { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: session.token }) }).catch(() => undefined);
    }
  }
  const response = await fetch(COMPOSE_EXIT_ENDPOINT, { method: "POST", body: exitDraftForm(draft), credentials: "same-origin" });
  if (!response.ok) throw new Error(`draft-save-${response.status}`);
  const result = await response.json() as { saved?: boolean; messageId?: number };
  if (!result.saved) throw new Error("draft-save-unconfirmed");
  await removeExitDraft(draft.key).catch(() => undefined);
  return result.messageId;
}
