/* ============================================================================
   PHOTOS — shared trip gallery. Capture/upload → downscaled JPEG stored in the
   Supabase `photos` bucket (+ row in the photos table), shared across the trip.
   A copy is cached in IndexedDB so photos still show offline. photoStore keeps
   the same API the screens use (list / add / update / remove / subscribe).
   ============================================================================ */
const PHOTO_KEY = "vn_trip_photos_v1"; // legacy (unused now)
const PLACE_GUESSES = ["Hanoi"];

/* downscale an image File to a <=1280px JPEG Blob (small, shareable) */
function fileToJpegBlob(file, max = 1280, q = 0.82) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      const scale = Math.min(1, max / Math.max(img.width, img.height));
      const w = Math.round(img.width * scale), h = Math.round(img.height * scale);
      const c = document.createElement("canvas");
      c.width = w; c.height = h;
      c.getContext("2d").drawImage(img, 0, 0, w, h);
      URL.revokeObjectURL(url);
      c.toBlob((b) => (b ? resolve(b) : reject(new Error("toBlob failed"))), "image/jpeg", q);
    };
    img.onerror = reject;
    img.src = url;
  });
}
/* kept for backward compatibility */
function fileToDataUrl(file, max = 1280) {
  return fileToJpegBlob(file, max).then((b) => new Promise((res) => { const r = new FileReader(); r.onload = () => res(r.result); r.readAsDataURL(b); }));
}

/* ---- IndexedDB offline cache for photo blobs (by photo id) ---- */
const _PDB = "vn_trip_photo_cache", _PS = "p";
let _pdbp = null;
function _pdb() {
  if (_pdbp) return _pdbp;
  _pdbp = new Promise((res, rej) => { const r = indexedDB.open(_PDB, 1); r.onupgradeneeded = () => r.result.createObjectStore(_PS); r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
  return _pdbp;
}
function _prun(mode, fn) { return _pdb().then((d) => new Promise((res, rej) => { const tx = d.transaction(_PS, mode); const s = tx.objectStore(_PS); const rq = fn(s); tx.oncomplete = () => res(rq && rq.result); tx.onerror = () => rej(tx.error); })); }
const _pPut = (id, b) => _prun("readwrite", (s) => s.put(b, id)).catch(() => {});
const _pGet = (id) => _prun("readonly", (s) => s.get(id)).catch(() => null);
const _pDel = (id) => _prun("readwrite", (s) => s.delete(id)).catch(() => {});
let _syncChain = Promise.resolve(); // serializes Google Photos uploads

const photoStore = {
  list: (window.db && window.db.cacheGet("photos_rows")) || [],
  subs: new Set(),
  _notify() { this.subs.forEach((f) => f()); },
  subscribe(f) { this.subs.add(f); return () => this.subs.delete(f); },

  async refresh() {
    const d = window.db; if (!d) return;
    const offline = navigator.onLine === false;
    if (!offline && d.sb && d.auth.session) {
      const { data, error } = await d.sb.from("photos").select("*").order("created_at", { ascending: false });
      if (!error && data) {
        d.cacheSet("photos_rows", data);
        const paths = data.filter((r) => r.file_path).map((r) => r.file_path);
        const map = {};
        if (paths.length) {
          try { const { data: sd } = await d.sb.storage.from("photos").createSignedUrls(paths, 3600); (sd || []).forEach((s) => { if (s && s.signedUrl) map[s.path] = s.signedUrl; }); } catch (e) {}
        }
        this.list = data.map((r) => ({ ...r, src: map[r.file_path] || null }));
        this._notify();
        return;
      }
    }
    // offline / fallback: cached rows + cached blobs
    const rows = d.cacheGet("photos_rows") || [];
    this.list = await Promise.all(rows.map(async (r) => { const blob = await _pGet(r.id); return { ...r, src: blob ? URL.createObjectURL(blob) : null }; }));
    this._notify();
  },

  async add(file, meta) {
    const d = window.db;
    const id = "ph" + Date.now() + Math.random().toString(36).slice(2, 6);
    let blob; try { blob = await fileToJpegBlob(file); } catch (e) { return; }
    try { await _pPut(id, blob); } catch (e) {}
    if (d && d.sb) {
      const path = id + ".jpg";
      const up = await d.sb.storage.from("photos").upload(path, blob, { upsert: true, contentType: "image/jpeg" });
      if (!up.error) {
        await d.sb.from("photos").insert({ id, caption: (meta && meta.caption) || "", place: (meta && meta.place) || "", day: (meta && meta.day) || "", file_path: path });
        await this.refresh();
        this.syncOne({ id, caption: (meta && meta.caption) || "" }, blob); // auto-backup to Google Photos
      }
    }
  },

  // ---- Google Photos backup (via /api/gphotos serverless function) ----
  syncOne(photo, blob) {
    if (!photo || !photo.id) return Promise.resolve(false);
    const self = this;
    const run = async () => {
      const b = blob || await getPhotoBlob(photo);
      if (!b) return false;
      try {
        const r = await fetch("/api/gphotos", { method: "POST",
          headers: { "x-filename": _photoName(photo), "x-content-type": b.type || "image/jpeg", "x-description": encodeURIComponent(photo.caption || "") },
          body: b });
        const d = await r.json().catch(() => ({}));
        if (d && d.ok) {
          const db = window.db;
          if (db && db.sb) { try { await db.sb.from("photos").update({ gphotos: true }).eq("id", photo.id); } catch (e) {} }
          self.list = self.list.map((x) => x.id === photo.id ? { ...x, gphotos: true } : x); self._notify();
          return true;
        }
      } catch (e) {}
      return false;
    };
    // serialize all Google Photos writes (avoids the concurrent-write 429)
    _syncChain = _syncChain.then(run, run);
    return _syncChain;
  },
  async syncAll(onProgress) {
    const pending = this.list.filter((p) => !p.gphotos);
    let done = 0;
    for (const p of pending) { if (await this.syncOne(p)) done++; if (onProgress) onProgress(done, pending.length); }
    return { done, total: pending.length };
  },

  async update(id, patch) {
    const d = window.db;
    this.list = this.list.map((x) => x.id === id ? { ...x, ...patch } : x); this._notify();
    if (d && d.sb) await d.sb.from("photos").update(patch).eq("id", id);
  },

  async remove(id) {
    const d = window.db;
    try { await _pDel(id); } catch (e) {}
    const row = this.list.find((p) => p.id === id);
    this.list = this.list.filter((x) => x.id !== id); this._notify();
    if (d && d.sb) {
      if (row && row.file_path) { try { await d.sb.storage.from("photos").remove([row.file_path]); } catch (e) {} }
      await d.sb.from("photos").delete().eq("id", id);
    }
  },
};

function usePhotos() {
  const [, force] = React.useState(0);
  React.useEffect(() => { const u = photoStore.subscribe(() => force((n) => n + 1)); photoStore.refresh(); return u; }, []);
  return photoStore;
}

/* ---- get / share / download a photo's actual image bytes ---- */
async function getPhotoBlob(photo) {
  const cached = await _pGet(photo.id);
  if (cached) return cached;
  if (photo.src) { try { const r = await fetch(photo.src); if (r.ok) return await r.blob(); } catch (e) {} }
  return null;
}
function _downloadBlob(blob, name) {
  const u = URL.createObjectURL(blob);
  const a = document.createElement("a"); a.href = u; a.download = name;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(u), 1500);
}
function _photoName(photo) {
  const base = (photo.caption || photo.place || "vietnam-photo").replace(/[^\w]+/g, "-").slice(0, 40) || "photo";
  return base + ".jpg";
}
async function downloadPhoto(photo) {
  const blob = await getPhotoBlob(photo);
  if (blob) _downloadBlob(blob, _photoName(photo));
}
// Native share sheet (→ Google Photos app / WhatsApp / Save to camera roll); falls back to download.
async function sharePhoto(photo) {
  const blob = await getPhotoBlob(photo);
  if (!blob) return;
  const file = new File([blob], _photoName(photo), { type: blob.type || "image/jpeg" });
  try {
    if (navigator.canShare && navigator.canShare({ files: [file] })) {
      await navigator.share({ files: [file], title: photo.caption || "GAP Việt Nam" });
      return;
    }
  } catch (e) { if (e && e.name === "AbortError") return; }
  _downloadBlob(blob, file.name);
}

// live updates: refresh when another device changes photos
window.addEventListener("vn:realtime", (e) => { if (e.detail && e.detail.table === "photos") photoStore.refresh(); });

/* Two-button add bar (camera + library). Hidden inputs do the real work. */
function AddPhotoBar({ compact }) {
  const camRef = React.useRef(null);
  const libRef = React.useRef(null);
  const [busy, setBusy] = React.useState(false);

  const ingest = async (files) => {
    if (!files || !files.length) return;
    setBusy(true);
    const d = new Date();
    for (const f of files) {
      if (!f.type.startsWith("image/")) continue;
      try {
        await photoStore.add(f, {
          place: PLACE_GUESSES[photoStore.list.length % PLACE_GUESSES.length],
          day: d.toLocaleDateString("en-US", { month: "short", day: "numeric" }),
        });
      } catch (e) {}
    }
    setBusy(false);
  };

  return (
    <div style={{ display: "flex", gap: 10 }}>
      <input ref={camRef} type="file" accept="image/*" capture="environment" hidden
        onChange={(e) => { ingest(e.target.files); e.target.value = ""; }} />
      <input ref={libRef} type="file" accept="image/*" multiple hidden
        onChange={(e) => { ingest(e.target.files); e.target.value = ""; }} />
      <Btn kind="primary" icon="camera" onClick={() => camRef.current && camRef.current.click()} full>
        {busy ? "Adding…" : "Take photo"}
      </Btn>
      <Btn kind="ghost" icon="image_add" onClick={() => libRef.current && libRef.current.click()} full>
        Upload
      </Btn>
    </div>
  );
}

Object.assign(window, { photoStore, usePhotos, fileToDataUrl, fileToJpegBlob, AddPhotoBar, getPhotoBlob, downloadPhoto, sharePhoto });
