import { invoke } from "@tauri-apps/api/core";
import {
  loadSettings, saveSettings,
  loadLibrary,  saveLibrary,
  loadUpdates,  saveUpdates,
} from "$lib/core/persistence/persist";

async function collectStoreFiles(): Promise<{ name: string; bytes: Uint8Array }[]> {
  const [settings, library, updates] = await Promise.all([
    loadSettings(),
    loadLibrary(),
    loadUpdates(),
  ]);

  const enc = new TextEncoder();
  return [
    { name: "settings.json", bytes: enc.encode(JSON.stringify(settings)) },
    { name: "library.json",  bytes: enc.encode(JSON.stringify(library)) },
    { name: "updates.json",  bytes: enc.encode(JSON.stringify(updates)) },
  ];
}

export async function exportAppData(): Promise<void> {
  const zip = buildZip(await collectStoreFiles());
  await invoke("export_app_data", { bytes: Array.from(zip) });
}

export async function importAppData(): Promise<void> {
  const raw: number[] = await invoke("import_app_data");
  const files = parseZip(new Uint8Array(raw));

  const decode = (name: string) => {
    const bytes = files.get(name);
    if (!bytes) throw new Error(`Backup is missing ${name}`);
    return JSON.parse(new TextDecoder().decode(bytes));
  };

  const s = decode("settings.json");
  const l = decode("library.json");
  const u = decode("updates.json");

  await Promise.all([
    saveSettings({
      storeVersion: s.storeVersion ?? 2,
      settings:     s.settings     ?? null,
    }),
    saveLibrary({
      sessions:        l.sessions        ?? [],
      bookmarks:       l.bookmarks       ?? [],
      markers:         l.markers         ?? [],
      dailyReadCounts: l.dailyReadCounts ?? {},
    }),
    saveUpdates({
      libraryUpdates:        u.libraryUpdates        ?? [],
      lastLibraryRefresh:    u.lastLibraryRefresh    ?? 0,
      acknowledgedUpdateIds: u.acknowledgedUpdateIds ?? [],
    }),
  ]);

  await showExitModal();
  invoke("exit_app");
}

export async function autoBackupAppData(): Promise<void> {
  try {
    const zip = buildZip(await collectStoreFiles());
    await invoke("auto_backup_app_data", { bytes: Array.from(zip) });
  } catch (e) {
    console.warn("[moku] auto-backup failed:", e);
  }
}

function showExitModal(): Promise<void> {
  return new Promise(resolve => {
    const backdrop = document.createElement("div");
    backdrop.className = "s-backdrop";
    backdrop.style.cssText = "z-index:99999";

    const modal = document.createElement("div");
    modal.style.cssText = [
      "background:var(--bg-surface)",
      "border:1px solid var(--border-base)",
      "border-radius:var(--radius-2xl)",
      "box-shadow:0 0 0 1px rgba(255,255,255,0.04) inset,0 24px 80px rgba(0,0,0,0.7),0 8px 24px rgba(0,0,0,0.4)",
      "width:min(400px,calc(100vw - 40px))",
      "display:flex",
      "flex-direction:column",
      "overflow:hidden",
      "animation:s-scale-in 0.2s cubic-bezier(0.16,1,0.3,1) both",
    ].join(";");

    const header = document.createElement("div");
    header.style.cssText = "padding:var(--sp-4) var(--sp-5) var(--sp-3);border-bottom:1px solid var(--border-dim)";

    const title = document.createElement("p");
    title.style.cssText = "margin:0;font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--text-primary);letter-spacing:0.01em";
    title.textContent = "Import complete";
    header.appendChild(title);

    const body = document.createElement("div");
    body.style.cssText = "padding:var(--sp-4) var(--sp-5);display:flex;flex-direction:column;gap:var(--sp-2)";

    const sub = document.createElement("p");
    sub.style.cssText = "margin:0;font-family:var(--font-ui);font-size:var(--text-xs);color:var(--text-faint);letter-spacing:var(--tracking-wide);line-height:var(--leading-snug)";
    sub.textContent = "Your settings have been restored. Moku will close so you can relaunch with the imported data.";

    const counter = document.createElement("p");
    counter.style.cssText = "margin:0;font-family:var(--font-ui);font-size:var(--text-xs);color:var(--text-faint);letter-spacing:var(--tracking-wide)";
    counter.textContent = "Closing in 3…";

    body.append(sub, counter);

    const footer = document.createElement("div");
    footer.style.cssText = "padding:var(--sp-3) var(--sp-5);border-top:1px solid var(--border-dim);display:flex;justify-content:flex-end";

    const btn = document.createElement("button");
    btn.className = "s-btn s-btn-danger";
    btn.textContent = "Close now";

    footer.appendChild(btn);
    modal.append(header, body, footer);
    backdrop.appendChild(modal);
    document.body.appendChild(backdrop);

    let secs = 3;
    const tick = setInterval(() => {
      secs--;
      counter.textContent = secs > 0 ? `Closing in ${secs}…` : "Closing…";
      if (secs <= 0) { clearInterval(tick); backdrop.remove(); resolve(); }
    }, 1000);

    btn.addEventListener("click", () => { clearInterval(tick); backdrop.remove(); resolve(); });
  });
}

function crc32(data: Uint8Array): number {
  let crc = 0xffffffff;
  for (const byte of data) {
    crc ^= byte;
    for (let j = 0; j < 8; j++) {
      crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
    }
  }
  return (crc ^ 0xffffffff) >>> 0;
}

function localHeader(name: Uint8Array, data: Uint8Array): Uint8Array {
  const buf = new ArrayBuffer(30 + name.byteLength);
  const v   = new DataView(buf);
  v.setUint32(0,  0x04034b50,      true);
  v.setUint16(4,  20,              true);
  v.setUint16(6,  0,               true);
  v.setUint16(8,  0,               true);
  v.setUint16(10, 0,               true);
  v.setUint16(12, 0,               true);
  v.setUint32(14, crc32(data),     true);
  v.setUint32(18, data.byteLength, true);
  v.setUint32(22, data.byteLength, true);
  v.setUint16(26, name.byteLength, true);
  v.setUint16(28, 0,               true);
  new Uint8Array(buf).set(name, 30);
  return new Uint8Array(buf);
}

function centralHeader(name: Uint8Array, data: Uint8Array, offset: number): Uint8Array {
  const buf = new ArrayBuffer(46 + name.byteLength);
  const v   = new DataView(buf);
  v.setUint32(0,  0x02014b50,      true);
  v.setUint16(4,  20,              true);
  v.setUint16(6,  20,              true);
  v.setUint16(8,  0,               true);
  v.setUint16(10, 0,               true);
  v.setUint16(12, 0,               true);
  v.setUint16(14, 0,               true);
  v.setUint32(16, crc32(data),     true);
  v.setUint32(20, data.byteLength, true);
  v.setUint32(24, data.byteLength, true);
  v.setUint16(28, name.byteLength, true);
  v.setUint16(30, 0,               true);
  v.setUint16(32, 0,               true);
  v.setUint16(34, 0,               true);
  v.setUint16(36, 0,               true);
  v.setUint32(38, 0,               true);
  v.setUint32(42, offset,          true);
  new Uint8Array(buf).set(name, 46);
  return new Uint8Array(buf);
}

function eocd(count: number, cdSize: number, cdOffset: number): Uint8Array {
  const buf = new ArrayBuffer(22);
  const v   = new DataView(buf);
  v.setUint32(0,  0x06054b50, true);
  v.setUint16(4,  0,          true);
  v.setUint16(6,  0,          true);
  v.setUint16(8,  count,      true);
  v.setUint16(10, count,      true);
  v.setUint32(12, cdSize,     true);
  v.setUint32(16, cdOffset,   true);
  v.setUint16(20, 0,          true);
  return new Uint8Array(buf);
}

function buildZip(files: { name: string; bytes: Uint8Array }[]): Uint8Array {
  const enc     = new TextEncoder();
  const parts:   Uint8Array[] = [];
  const offsets: number[]     = [];
  let pos = 0;

  for (const { name, bytes } of files) {
    const nameBytes = enc.encode(name);
    const lh        = localHeader(nameBytes, bytes);
    offsets.push(pos);
    parts.push(lh, bytes);
    pos += lh.byteLength + bytes.byteLength;
  }

  const cdParts = files.map(({ name, bytes }, i) =>
    centralHeader(enc.encode(name), bytes, offsets[i])
  );
  const cd = concat(cdParts);

  return concat([...parts, cd, eocd(files.length, cd.byteLength, pos)]);
}

function parseZip(data: Uint8Array): Map<string, Uint8Array> {
  const view  = new DataView(data.buffer, data.byteOffset, data.byteLength);
  const files = new Map<string, Uint8Array>();
  let pos     = 0;

  while (pos + 30 <= data.byteLength && view.getUint32(pos, true) === 0x04034b50) {
    const fnLen = view.getUint16(pos + 26, true);
    const exLen = view.getUint16(pos + 28, true);
    const cSize = view.getUint32(pos + 18, true);
    const name  = new TextDecoder().decode(data.subarray(pos + 30, pos + 30 + fnLen));
    const start = pos + 30 + fnLen + exLen;
    files.set(name, data.subarray(start, start + cSize));
    pos = start + cSize;
  }

  return files;
}

function concat(arrays: Uint8Array[]): Uint8Array {
  const total = arrays.reduce((n, a) => n + a.byteLength, 0);
  const out   = new Uint8Array(total);
  let pos     = 0;
  for (const a of arrays) { out.set(a, pos); pos += a.byteLength; }
  return out;
}