/*__IIFE_WRAPPED__*/
;(function(){
// firebase-client.jsx — Firebase SDK初期化 + メール認証コード認証 + callable/リスナー
//
// 認証はメールアドレス＋6桁の認証コード。パスワードは一切扱わない。
// コード送信・検証はサーバー (requestEmailCode/verifyEmailCode) で行い、
// 検証成功時に返るカスタムトークンで signInWithCustomToken する。
// API呼び出しはすべて httpsCallable（トークンは SDK が自動付与・サーバー側で検証）。
// 別環境へデプロイする場合はこの FIREBASE_CONFIG だけ差し替える:
//   firebase apps:sdkconfig web --project <projectId>

const FIREBASE_CONFIG = {
  apiKey: "AIzaSyDx0MsDYa1EF6zZRYWt1Rr03hjLr1CbW38",
  authDomain: "fair-torus-498607-e7.firebaseapp.com",
  projectId: "fair-torus-498607-e7",
  storageBucket: "fair-torus-498607-e7.firebasestorage.app",
  messagingSenderId: "951564206213",
  appId: "1:951564206213:web:a75ebd8d8438ca8c3aa9ec",
};

const REGION = "asia-northeast1";
const EMAIL_KEY = "amulet_email_for_signin"; // コード入力画面でのメアド保持用

// ── 初期化（?emulator=1 か localhost でエミュレータ接続）──
let _app, _auth, _db, _fns;
function fb() {
  if (_app) return { auth: _auth, db: _db, fns: _fns };
  _app = firebase.initializeApp(FIREBASE_CONFIG);
  _auth = firebase.auth();
  _db = firebase.firestore();
  _fns = firebase.app().functions(REGION);
  const wantEmu = new URLSearchParams(location.search).has("emulator");
  if (wantEmu) {
    _auth.useEmulator("http://localhost:9099");
    _db.useEmulator("localhost", 8080);
    _fns.useEmulator("localhost", 5001);
  }
  return { auth: _auth, db: _db, fns: _fns };
}

function call(name, data) {
  // GA4計装: どの callable を呼んだかだけ送る（引数の中身=本文/PIIは絶対に送らない）
  window.track && window.track('api_call', { api_name: name });
  return fb().fns.httpsCallable(name)(data ?? {}).then(r => r.data);
}

/* ════════ 認証（メール認証コード）════════ */

// メアド宛に6桁コードを送る（test@example.com はサーバー側でメールを送らず 000000 固定）
async function requestEmailCode(email) {
  await call("requestEmailCode", { email });
  localStorage.setItem(EMAIL_KEY, email);
}

// コードを検証し、返ったカスタムトークンでサインインする
async function verifyEmailCode(email, code) {
  const { auth } = fb();
  const { token } = await call("verifyEmailCode", { email, code });
  await auth.signInWithCustomToken(token);
  localStorage.removeItem(EMAIL_KEY);
}

function onAuth(cb) {
  return fb().auth.onAuthStateChanged(cb);
}

async function signOut() {
  localStorage.removeItem(EMAIL_KEY);
  await fb().auth.signOut();
}

/* ════════ users ════════ */

// 初回サインイン時の users/{uid} 作成（既存なら何もしない）
async function ensureUserDoc() {
  const { auth, db } = fb();
  const u = auth.currentUser;
  if (!u) return;
  const ref = db.doc(`users/${u.uid}`);
  const snap = await ref.get();
  if (!snap.exists) {
    await ref.set({ email: u.email ?? "", name: "", coupleId: null,
      createdAt: firebase.firestore.FieldValue.serverTimestamp() });
  }
}

function saveName(name) {
  const u = fb().auth.currentUser;
  return fb().db.doc(`users/${u.uid}`).update({
    name, updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
  });
}

// プロフィールの一部だけ更新（最寄駅など）。rules で許可されたキーのみ通る。
function saveProfile(patch) {
  const u = fb().auth.currentUser;
  return fb().db.doc(`users/${u.uid}`).update({
    ...patch, updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
  });
}

function listenUser(cb) {
  const u = fb().auth.currentUser;
  if (!u) return () => {};
  return fb().db.doc(`users/${u.uid}`).onSnapshot(
    s => cb(s.exists ? { uid: u.uid, ...s.data() } : null),
    e => console.error("listenUser:", e),
  );
}

async function fetchPartner(coupleId) {
  const { auth, db } = fb();
  const couple = await db.doc(`couples/${coupleId}`).get();
  const members = couple.get("members") || [];
  const partnerUid = members.find(m => m !== auth.currentUser.uid);
  if (!partnerUid) return null;
  const p = await db.doc(`users/${partnerUid}`).get();
  return { uid: partnerUid, name: p.get("name") || "パートナー",
    nearestStation: p.get("nearestStation") || "", members };
}

// couple ドキュメント（since=付き合った日 等）を購読。設定変更も即反映される。
function listenCouple(coupleId, cb) {
  return fb().db.doc(`couples/${coupleId}`).onSnapshot(
    s => cb(s.exists ? { id: s.id, ...s.data() } : null),
    e => console.error("listenCouple:", e));
}

// 付き合った日を保存（本人カップルのみ・rules で since/anniversaries のみ許可）
function setAnniversary(coupleId, since) {
  return fb().db.doc(`couples/${coupleId}`).set({ since }, { merge: true });
}

/* ════════ events ════════ */

function listenEvents(coupleId, cb) {
  return fb().db.collection(`couples/${coupleId}/events`)
    .orderBy("createdAt", "desc").limit(20)
    .onSnapshot(
      qs => cb(qs.docs.map(d => ({ id: d.id, ...d.data() }))),
      e => console.error("listenEvents:", e),
    );
}

function listenEvent(coupleId, eventId, cb) {
  return fb().db.doc(`couples/${coupleId}/events/${eventId}`)
    .onSnapshot(s => cb(s.exists ? { id: s.id, ...s.data() } : null),
      e => console.error("listenEvent:", e));
}

async function createEvent(coupleId, data) {
  const u = fb().auth.currentUser;
  const ref = await fb().db.collection(`couples/${coupleId}/events`).add({
    ...data,
    planStatus: "none",
    genCount: 0,
    createdBy: u.uid,
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
  });
  return ref.id;
}

// 基本情報(タイトル/日付/エリア/時間帯)の後追い編集。
// planStatus / genCount / inputStats / lastGenRequestedBy はサーバー管理のため
// firestore.rules 側で更新が拒否される（それら以外のフィールドだけ渡すこと）。
async function updateEvent(coupleId, eventId, patch) {
  // undefined は Firestore が受け付けないので落とす
  const clean = Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined));
  await fb().db.doc(`couples/${coupleId}/events/${eventId}`).update(clean);
}

// デート（イベント）を削除。firestore.rules で isMember なら delete 許可。
// サブコレクション（inputs/plan/plans/...）はクライアントでは消せないが、
// 一覧・詳細から辿れなくなるので実用上は消えたのと同じ。
function deleteEvent(coupleId, eventId) {
  return fb().db.doc(`couples/${coupleId}/events/${eventId}`).delete();
}

function listenPlan(coupleId, eventId, cb) {
  return fb().db.doc(`couples/${coupleId}/events/${eventId}/plan/current`)
    .onSnapshot(s => cb(s.exists ? s.data() : null),
      e => console.error("listenPlan:", e));
}

// 新: バージョン管理されたプラン JSON。最新版（version 最大）を購読する。
// 書き換え（planChat）で新バージョンが生まれると自動で差し替わる。
// 互換: plans/ がまだ無い旧データは plan/current にフォールバックする。
function listenLatestPlan(coupleId, eventId, cb) {
  const base = `couples/${coupleId}/events/${eventId}`;
  return fb().db.collection(`${base}/plans`)
    .orderBy("version", "desc").limit(1)
    .onSnapshot(qs => {
      if (!qs.empty) { cb(qs.docs[0].data()); return; }
      fb().db.doc(`${base}/plan/current`).get()
        .then(s => cb(s.exists ? s.data() : null))
        .catch(() => cb(null));
    }, e => console.error("listenLatestPlan:", e));
}

async function getMyInput(coupleId, eventId) {
  const u = fb().auth.currentUser;
  const s = await fb().db.doc(`couples/${coupleId}/events/${eventId}/inputs/${u.uid}`).get();
  return s.exists ? s.data() : null;
}

// ③ プラン相談の本人スレッド（相手には見えない）
async function getMyPlanChat(coupleId, eventId) {
  const u = fb().auth.currentUser;
  const s = await fb().db.doc(`couples/${coupleId}/events/${eventId}/planChat/${u.uid}`).get();
  return s.exists ? s.data() : null;
}

async function getSurprise(coupleId, eventId) {
  const u = fb().auth.currentUser;
  const s = await fb().db.doc(`couples/${coupleId}/events/${eventId}/surprises/${u.uid}`).get();
  return s.exists ? s.data() : null;
}

/* ════════ pins / wishlist ════════ */

function listenPins(coupleId, cb) {
  return fb().db.collection(`couples/${coupleId}/pins`)
    .orderBy("createdAt", "desc").limit(50)
    .onSnapshot(qs => cb(qs.docs.map(d => ({ id: d.id, ...d.data() }))),
      e => console.error("listenPins:", e));
}

function addPin(coupleId, pin) {
  const u = fb().auth.currentUser;
  // undefined は Firestore が受け付けないので落とす（photoUrl/placeId 等の任意フィールド対策）
  const clean = Object.fromEntries(Object.entries(pin).filter(([, v]) => v !== undefined));
  return fb().db.collection(`couples/${coupleId}/pins`).add({
    ...clean,
    addedBy: u.uid,
    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
  }).then(ref => { window.track && window.track('pin_add'); return ref; });
}

function deletePin(coupleId, pinId) {
  return fb().db.doc(`couples/${coupleId}/pins/${pinId}`).delete();
}

function listenWishlist(coupleId, cb) {
  const u = fb().auth.currentUser;
  return fb().db.doc(`couples/${coupleId}/wishlists/${u.uid}`)
    .onSnapshot(s => cb(s.exists ? (s.get("items") || []) : []),
      e => console.error("listenWishlist:", e));
}

function saveWishlist(coupleId, items) {
  const u = fb().auth.currentUser;
  return fb().db.doc(`couples/${coupleId}/wishlists/${u.uid}`).set({
    items, updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
  }, { merge: true });
}

/* ════════ 画像ユーティリティ ════════ */

// File → 縮小済み base64（長辺1024px / JPEG）。callable のペイロード上限対策。
function fileToBase64(file, maxEdge = 1024) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      const scale = Math.min(1, maxEdge / Math.max(img.width, img.height));
      const canvas = document.createElement("canvas");
      canvas.width = Math.round(img.width * scale);
      canvas.height = Math.round(img.height * scale);
      canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
      URL.revokeObjectURL(url);
      const dataUrl = canvas.toDataURL("image/jpeg", 0.84);
      resolve({ base64: dataUrl.split(",")[1], mimeType: "image/jpeg" });
    };
    img.onerror = reject;
    img.src = url;
  });
}

/* ════════ エラーメッセージ ════════ */

function friendlyError(e) {
  const msg = e?.message || "";
  // HttpsError は日本語メッセージをそのまま出す。SDK内部エラーは丸める。
  if (e?.code?.startsWith?.("functions/") && msg && !/INTERNAL|internal/.test(msg)) return msg;
  if (/network|fetch|Failed to/i.test(msg)) return "通信に失敗しました。電波のよいところでもう一度。";
  return "うまくいきませんでした。もう一度試してください。";
}

/* ════════ 公開API ════════ */

const Amulet = {
  // auth
  requestEmailCode, verifyEmailCode, onAuth, signOut,
  currentUser: () => fb().auth.currentUser,
  // users / couple
  ensureUserDoc, saveName, saveProfile, listenUser, fetchPartner, listenCouple, setAnniversary,
  // pairing (callable)
  createInvite: () => call("createInvite"),
  redeemInvite: (code) => call("redeemInvite", { code }),
  // events
  listenEvents, listenEvent, createEvent, updateEvent, deleteEvent,
  listenPlan, listenLatestPlan, getMyInput, getMyPlanChat, getSurprise,
  // chat / plan (callable)
  sendChat: (p) => call("sendChat", p),                          // ① 本音・相談役（counsel）
  planChat: (p) => call("planChat", p),                          // ③ プラン相談（会話。案が溜まると承認待ちに）
  applyPlanProposal: (eventId) => call("applyPlanProposal", { eventId }), // ③ 溜まった案を承認して作り直す
  generatePlan: (eventId, adjustment, requiredSpots) => call("generatePlan", { eventId, adjustment, requiredSpots }),
  detectSpot: (imageBase64, mimeType, chatContext, textQuery) =>
    call("detectSpot", {
      ...(imageBase64 ? { imageBase64, mimeType } : {}),
      chatContext,
      ...(textQuery ? { textQuery } : {}),
    }),
  // pins / wishlist
  listenPins, addPin, deletePin, listenWishlist, saveWishlist,
  // utils
  fileToBase64, friendlyError,
};

Object.assign(window, { Amulet });

})();
