/**
 * Jami – Cloudflare D1 (Standard live) · Supabase · localStorage (lokal).
 */
const JamiApi = {
  mode: 'local',
  sb: null,
  myProfile: null,
  _token: '',
  _profiles: [],
  _likesFrom: new Set(),
  _likesTo: new Set(),
  _passes: new Set(),
  _blocks: new Set(),
  _threads: {},
  _mutualIds: new Set(),
  _inboxSummary: null,

  async init(appConfig) {
    this._token = localStorage.getItem('jami_token') || '';
    const backend = (appConfig && appConfig.backend) || 'auto';
    if (backend === 'cloudflare') {
      this.mode = 'cloudflare';
      if (this._token) {
        try {
          await this._cloudLoadMe();
        } catch (e) {
          localStorage.removeItem('jami_token');
          this._token = '';
          this.myProfile = null;
        }
      }
      return this.mode;
    }
    const wantCloud = backend === 'auto';
    if (wantCloud) {
      try {
        const h = await fetch('/api/health');
        if (h.ok) {
          const j = await h.json();
          if (j.backend === 'cloudflare-d1') {
            this.mode = 'cloudflare';
            if (this._token) {
              try {
                await this._cloudLoadMe();
              } catch (e) {
                localStorage.removeItem('jami_token');
                this._token = '';
              }
            }
            return this.mode;
          }
        }
      } catch (e) {}
    }
    let sbCfg = { url: '', anonKey: '', enabled: false };
    try {
      const r = await fetch('js/supabase-config.json');
      if (r.ok) sbCfg = { ...sbCfg, ...(await r.json()) };
    } catch (e) {}
    const url = (sbCfg.url || '').trim();
    const key = (sbCfg.anonKey || '').trim();
    if (sbCfg.enabled && url && key) {
      const sb = window.supabase || (await this._loadSupabaseJs());
      if (!sb) {
        this.mode = 'local';
        return this.mode;
      }
      this.sb = sb.createClient(url, key);
      this.mode = 'supabase';
      const { data: { session } } = await this.sb.auth.getSession();
      if (session) await this._loadProfile(session.user.id);
      return this.mode;
    }
    this.mode = 'local';
    return this.mode;
  },

  isOnline() {
    return this.mode === 'cloudflare' || this.mode === 'supabase';
  },

  async _cloudFetch(path, opts = {}) {
    const headers = { 'Content-Type': 'application/json', ...(opts.headers || {}) };
    if (this._token) headers.Authorization = 'Bearer ' + this._token;
    const timeoutMs = opts.timeoutMs || 10000;
    const fetchOpts = { ...opts, headers };
    delete fetchOpts.timeoutMs;
    let ctrl = null;
    let timer = null;
    if (typeof AbortController !== 'undefined') {
      ctrl = new AbortController();
      fetchOpts.signal = ctrl.signal;
      timer = setTimeout(function () {
        ctrl.abort();
      }, timeoutMs);
    }
    let r;
    try {
      r = await fetch('/api/' + path, fetchOpts);
    } finally {
      if (timer) clearTimeout(timer);
    }
    let j = {};
    try {
      j = await r.json();
    } catch (e) {}
    if (!r.ok) throw new Error(j.error || 'Server-Fehler');
    return j;
  },

  async _cloudLoadMe() {
    const j = await this._cloudFetch('me');
    this.myProfile = j.profile;
    if (j.profileCompleteBonus) this._profileCompleteBonus = j.profileCompleteBonus;
    this.refreshSocial().catch(() => {});
    return j.profileCompleteBonus || null;
  },

  async _cloudLogin(email, password) {
    const j = await this._cloudFetch('login', { method: 'POST', body: JSON.stringify({ email, password }) });
    this._token = j.token;
    localStorage.setItem('jami_token', this._token);
    this.myProfile = j.profile;
    await this.refreshSocial();
    return { ok: true };
  },

  async _cloudRegister(payload) {
    const j = await this._cloudFetch('register', { method: 'POST', body: JSON.stringify(payload) });
    this._token = j.token;
    localStorage.setItem('jami_token', this._token);
    this.myProfile = j.profile;
    await this.refreshSocial();
    return { ok: true, referral: j.referral };
  },

  profileFromRow(row) {
    if (!row) return null;
    return {
      id: row.id,
      email: row.email || '',
      displayName: row.display_name,
      age: row.age,
      gender: row.gender,
      seeking: row.seeking,
      city: row.city,
      country: row.country,
      bio: row.bio || '',
      photo: row.photo_url || row.photo || '',
      photos: row.photos || (() => {
        try {
          const j = JSON.parse(row.photos_json || '{}');
          const unlockedGallery = Math.min(
            Math.max(parseInt(j.unlockedGallery, 10) || 4, 4),
            9
          );
          const unlockedLocked = Math.min(
            Math.max(parseInt(j.unlockedLocked, 10) || 5, 5),
            10
          );
          const gallery = [...(j.gallery || []), ...Array(9)].slice(0, 9).map((u) => u || '');
          const locked = [...(j.locked || []), ...Array(10)].slice(0, 10).map((u) => u ?? '');
          return {
            main: j.main || row.photo_url || '',
            gallery: gallery.slice(0, unlockedGallery),
            locked: locked.slice(0, unlockedLocked),
            lockedCount: unlockedLocked,
            unlockedGallery,
            unlockedLocked,
          };
        } catch (e) {
          return {
            main: row.photo_url || '',
            gallery: ['', '', '', ''],
            locked: [null, null, null, null, null],
            lockedCount: 5,
            unlockedGallery: 4,
            unlockedLocked: 5,
          };
        }
      })(),
      friendshipStatus: row.friendshipStatus || 'none',
      prompt: row.prompt || '',
      interests: row.interests || [],
      likes: row.likes || '',
      hobbies: (() => {
        if (!row.hobbies) return [];
        if (Array.isArray(row.hobbies)) return row.hobbies;
        try {
          return JSON.parse(row.hobbies || '[]');
        } catch (e) {
          return String(row.hobbies)
            .split(',')
            .map((s) => s.trim())
            .filter(Boolean);
        }
      })(),
      preferences: row.preferences || '',
      relationshipGoal: row.relationship_goal || '',
      birthday: row.birthday || '',
      hasPets: row.has_pets || '',
      petsInfo: row.pets_info || '',
      hasChildren: row.has_children || '',
      childrenInfo: row.children_info || '',
      smoker: row.smoker || '',
      lat: row.lat,
      lon: row.lon,
      coins: row.coins ?? 30,
      ownedEmojis: row.owned_emojis || [],
      profileEmojiId: row.profileEmojiId || row.profile_emoji_id || null,
      profileCompleteBonus: !!(row.profile_complete_bonus || row.profileCompleteBonus),
      profileBoostUntil: row.profileBoostUntil || row.profile_boost_until || null,
      profileBoostActive: !!(
        row.profileBoostActive ??
        (() => {
          const until = row.profileBoostUntil || row.profile_boost_until;
          if (!until) return false;
          const s = String(until).trim();
          const t = Date.parse(s.includes('T') ? s : s.replace(' ', 'T') + 'Z');
          return Number.isFinite(t) && t > Date.now();
        })()
      ),
      activityBadges: (() => {
        if (Array.isArray(row.activityBadges)) return row.activityBadges;
        if (row.activity_badges_json) {
          try {
            const v = JSON.parse(row.activity_badges_json);
            return Array.isArray(v) ? v : [];
          } catch (e) {
            return [];
          }
        }
        return [];
      })(),
      createdAt: row.createdAt || row.created_at || null,
      lastActiveAt: row.lastActiveAt || row.last_active_at || null,
      online: !!row.online,
      presenceText: row.presenceText || '',
      presenceTier: row.presenceTier || '',
      verified: row.verified,
      vipGold: row.vipGold ?? row.vip_gold ?? false,
      realPerson: row.real_person,
      demo: row.is_demo,
    };
  },

  profileToRow(p) {
    return {
      display_name: p.displayName,
      age: p.age,
      gender: p.gender,
      seeking: p.seeking,
      city: p.city,
      country: p.country,
      bio: p.bio || '',
      photo_url: p.photo || (p.photos && p.photos.main) || '',
      photos_json: p.photos ? JSON.stringify({
        main: p.photos.main || '',
        gallery: [...(p.photos.gallery || []), '', '', '', ''].slice(0, 4),
        locked: [...(p.photos.locked || []), '', '', '', '', ''].slice(0, 5),
      }) : undefined,
      prompt: p.prompt || '',
      interests: p.interests || [],
      likes: p.likes || '',
      hobbies: p.hobbies || [],
      preferences: p.preferences || '',
      relationship_goal: p.relationshipGoal || '',
      birthday: p.birthday || '',
      has_pets: p.hasPets || '',
      pets_info: p.petsInfo || '',
      has_children: p.hasChildren || '',
      children_info: p.childrenInfo || '',
      smoker: p.smoker || '',
      lat: p.lat,
      lon: p.lon,
      coins: p.coins,
      owned_emojis: p.ownedEmojis || [],
      profile_emoji_id: p.profileEmojiId || null,
      verified: p.verified !== false,
      real_person: p.realPerson !== false,
    };
  },

  async _loadProfile(authUserId) {
    const { data, error } = await this.sb
      .from('profiles')
      .select('*')
      .eq('auth_user_id', authUserId)
      .maybeSingle();
    if (error) throw error;
    this.myProfile = this.profileFromRow(data);
    if (this.myProfile) await this.refreshSocial();
    return this.myProfile;
  },

  async login(email, password) {
    if (this.mode === 'cloudflare') {
      try {
        return await this._cloudLogin(email, password);
      } catch (e) {
        return { ok: false, error: e.message };
      }
    }
    const { data, error } = await this.sb.auth.signInWithPassword({ email, password });
    if (error) return { ok: false, error: error.message };
    await this._loadProfile(data.user.id);
    if (!this.myProfile) return { ok: false, error: 'Kein Profil – bitte Support' };
    return { ok: true };
  },

  async register(payload) {
    if (this.mode === 'cloudflare') {
      try {
        return await this._cloudRegister(payload);
      } catch (e) {
        return { ok: false, error: e.message };
      }
    }
    const { data: authData, error: authErr } = await this.sb.auth.signUp({
      email: payload.email,
      password: payload.password,
    });
    if (authErr) return { ok: false, error: authErr.message };
    const uid = authData.user?.id;
    if (!uid) return { ok: false, error: 'Registrierung fehlgeschlagen' };
    const row = {
      auth_user_id: uid,
      is_demo: false,
      email: payload.email,
      ...this.profileToRow(payload),
      coins: payload.coins ?? 30,
    };
    const { data: prof, error: profErr } = await this.sb.from('profiles').insert(row).select().single();
    if (profErr) return { ok: false, error: profErr.message };
    this.myProfile = this.profileFromRow(prof);
    await this.refreshSocial();
    return { ok: true };
  },

  async logout() {
    if (this.mode === 'cloudflare') {
      localStorage.removeItem('jami_token');
      this._token = '';
      this.myProfile = null;
      return;
    }
    if (this.sb) await this.sb.auth.signOut();
    this.myProfile = null;
    this._profiles = [];
  },

  async updateMyProfile(patch) {
    if (!this.myProfile) return;
    if (this.mode === 'cloudflare') {
      Object.assign(this.myProfile, patch);
      const j = await this._cloudFetch('me', { method: 'PATCH', body: JSON.stringify(patch) });
      this.myProfile = j.profile;
      return j.profileCompleteBonus || null;
    }
    Object.assign(this.myProfile, patch);
    const { error } = await this.sb
      .from('profiles')
      .update(this.profileToRow(this.myProfile))
      .eq('id', this.myProfile.id);
    if (error) throw error;
  },

  async updateCoins(delta) {
    if (!this.myProfile) return;
    this.myProfile.coins = Math.max(0, (this.myProfile.coins || 0) + delta);
    if (this.mode === 'cloudflare') {
      await this.updateMyProfile({ coins: this.myProfile.coins });
      return;
    }
    await this.sb.from('profiles').update({ coins: this.myProfile.coins }).eq('id', this.myProfile.id);
  },

  myLikeIds() {
    if (this.mode === 'cloudflare') return [...this._likesFrom];
    return this.mode === 'supabase' ? [...this._likesFrom] : [];
  },
  myPassIds() {
    if (this.mode === 'cloudflare') return [...this._passes];
    return this.mode === 'supabase' ? [...this._passes] : [];
  },
  myBlockIds() {
    if (this.mode === 'cloudflare') return [...this._blocks];
    return this.mode === 'supabase' ? [...this._blocks] : [];
  },
  theyLikedMe(id) {
    return this._likesTo.has(id);
  },
  isMutual(id) {
    return this._likesFrom.has(id) && this._likesTo.has(id);
  },

  async refreshSocial() {
    if (this.mode === 'cloudflare') {
      const [inc, sent, mut, blk] = await Promise.all([
        this._cloudFetch('likes'),
        this._cloudFetch('likes/sent'),
        this._cloudFetch('likes/mutual'),
        this._cloudFetch('blocks'),
      ]);
      this._likesTo = new Set((inc.items || []).map((p) => p.id));
      if (!this._profileCache) this._profileCache = {};
      (inc.items || []).forEach((p) => {
        this._profileCache[p.id] = p;
      });
      this._likesFrom = new Set(sent.ids || []);
      (mut.items || []).forEach((p) => {
        this._profileCache[p.id] = p;
      });
      this._mutualIds = new Set((mut.items || []).map((p) => p.id));
      this._passes = this._passes || new Set();
      this._blocks = new Set(blk.ids || []);
      return;
    }
    if (!this.myProfile) return;
    const pid = this.myProfile.id;
    const [likes, passes, blocks, profiles] = await Promise.all([
      this.sb.from('likes').select('from_profile, to_profile, super_like'),
      this.sb.from('passes').select('to_profile').eq('from_profile', pid),
      this.sb.from('blocks').select('to_profile').eq('from_profile', pid),
      this.sb.from('profiles').select('*'),
    ]);
    this._profiles = (profiles.data || []).map(r => this.profileFromRow(r));
    this._likesFrom = new Set();
    this._likesTo = new Set();
    (likes.data || []).forEach(l => {
      if (l.from_profile === pid) this._likesFrom.add(l.to_profile);
      if (l.to_profile === pid) this._likesTo.add(l.from_profile);
    });
    this._passes = new Set((passes.data || []).map(p => p.to_profile));
    this._blocks = new Set((blocks.data || []).map(b => b.to_profile));
  },

  async getDiscoverQueueCloud(filters) {
    const q = new URLSearchParams({
      km: filters.km,
      ageMin: filters.ageMin,
      ageMax: filters.ageMax,
    });
    if (filters.everywhere) q.set('everywhere', '1');
    const j = await this._cloudFetch('discover?' + q);
    this._profileCache = {};
    const list = (j.items || []).map(x => {
      this._profileCache[x.profile.id] = x.profile;
      return { o: x.profile, km: x.km };
    });
    return list;
  },

  async getWmMatches() {
    return this._cloudFetch('wm/matches');
  },

  async placeWmBet(matchId, pick, stake) {
    const j = await this._cloudFetch('wm/bet', {
      method: 'POST',
      body: JSON.stringify({ matchId, pick, stake }),
    });
    if (j.coins != null && this.myProfile) this.myProfile.coins = j.coins;
    return j;
  },

  async getWmFanChat(after) {
    const q = after ? '?after=' + encodeURIComponent(after) : '';
    return this._cloudFetch('wm/fan-chat' + q);
  },

  async postWmFanChat(text) {
    return this._cloudFetch('wm/fan-chat', {
      method: 'POST',
      body: JSON.stringify({ text }),
    });
  },

  async reactWmFanChat(messageId, emoji) {
    return this._cloudFetch('wm/fan-chat/react', {
      method: 'POST',
      body: JSON.stringify({ messageId, emoji }),
    });
  },

  findProfile(id) {
    if (this._profileCache && this._profileCache[id]) return this._profileCache[id];
    if (this.myProfile && this.myProfile.id === id) return this.myProfile;
    return null;
  },

  async recordPass(otherId) {
    if (this.mode === 'cloudflare') {
      await this._cloudFetch('pass', { method: 'POST', body: JSON.stringify({ toId: otherId }) });
      this._passes.add(otherId);
      return;
    }
    if (!this._passes.has(otherId)) {
      await this.sb.from('passes').insert({ from_profile: this.myProfile.id, to_profile: otherId });
      this._passes.add(otherId);
    }
  },

  async recordLike(otherId, superLike) {
    if (this.mode === 'cloudflare') {
      const j = await this._cloudFetch('like', {
        method: 'POST',
        body: JSON.stringify({ toId: otherId, super: !!superLike }),
      });
      this._likesFrom.add(otherId);
      if (j.mutual) this._likesTo.add(otherId);
      if (j.profile) {
        if (!this._profileCache) this._profileCache = {};
        this._profileCache[j.profile.id] = j.profile;
      }
      if (superLike) this.myProfile.coins = j.profile?.coins ?? this.myProfile.coins;
      return { mutual: j.mutual, other: j.profile };
    }
    if (!this._likesFrom.has(otherId)) {
      await this.sb.from('likes').insert({
        from_profile: this.myProfile.id,
        to_profile: otherId,
        super_like: !!superLike,
      });
      this._likesFrom.add(otherId);
    }
    const other = this._profiles.find(p => p.id === otherId);
    const mutual = this.isMutual(otherId);
    return { mutual, other };
  },

  async blockUser(otherId) {
    if (this.mode === 'cloudflare') {
      await this._cloudFetch('block', { method: 'POST', body: JSON.stringify({ toId: otherId }) });
      this._blocks.add(otherId);
      this._likesFrom.delete(otherId);
      this._likesTo.delete(otherId);
      return;
    }
    if (!this._blocks.has(otherId)) {
      await this.sb.from('blocks').insert({ from_profile: this.myProfile.id, to_profile: otherId });
      this._blocks.add(otherId);
    }
  },

  async reportUser(otherId, reason) {
    if (this.mode === 'cloudflare') return;
    await this.sb.from('reports').insert({
      reporter: this.myProfile.id,
      reported: otherId,
      reason: reason || 'Meldung aus App',
    });
  },

  async getIncomingLikesCloud() {
    const j = await this._cloudFetch('likes');
    return j.items || [];
  },

  async fetchMutualMatchesCloud() {
    const j = await this._cloudFetch('likes/mutual');
    if (!this._profileCache) this._profileCache = {};
    (j.items || []).forEach((p) => {
      this._profileCache[p.id] = p;
    });
    return j.items || [];
  },

  async fetchProfileCloud(id) {
    const j = await this._cloudFetch('profile/' + encodeURIComponent(id));
    if (j.profile) {
      if (!this._profileCache) this._profileCache = {};
      this._profileCache[j.profile.id] = j.profile;
      return j.profile;
    }
    return null;
  },

  async sendPresenceHeartbeat() {
    if (this.mode !== 'cloudflare' || !this._token) return null;
    return this._cloudFetch('presence', { method: 'POST', body: '{}' });
  },

  async activateProfileBoost() {
    const j = await this._cloudFetch('profile-boost', { method: 'POST', body: '{}' });
    if (this.myProfile) {
      if (j.coins != null) this.myProfile.coins = j.coins;
      if (j.profileBoostUntil != null) this.myProfile.profileBoostUntil = j.profileBoostUntil;
      this.myProfile.profileBoostActive = !!j.profileBoostActive;
    }
    return j;
  },

  async uploadPhoto(dataUrl, { slot = 'main', index = 0 } = {}) {
    const j = await this._cloudFetch('photo-upload', {
      method: 'POST',
      body: JSON.stringify({ dataUrl, slot, index }),
    });
    if (this.myProfile) {
      if (j.photos) this.myProfile.photos = j.photos;
      if (j.photo != null) this.myProfile.photo = j.photo;
      else if (j.url) this.myProfile.photo = j.url;
    }
    return j.url || '';
  },

  async buyPhotoSlot(type) {
    const j = await this._cloudFetch('photo-slot', {
      method: 'POST',
      body: JSON.stringify({ type }),
    });
    if (this.myProfile) {
      if (j.coins != null) this.myProfile.coins = j.coins;
      if (j.photos) this.myProfile.photos = j.photos;
    }
    return j;
  },

  getDiscoverQueue(filters, helpers) {
    const u = this.myProfile;
    const liked = this.myLikeIds();
    const passed = this.myPassIds();
    const blocked = this.myBlockIds();
    return this._profiles
      .filter(o => {
        if (o.id === u.id || blocked.includes(o.id)) return false;
        if (o.demo || (o.id && String(o.id).startsWith('demo_'))) return false;
        if (passed.includes(o.id)) return false;
        if (!helpers.matchesSeeking(u, o)) return false;
        if (o.age < filters.ageMin || o.age > filters.ageMax) return false;
        if (filters.everywhere || u.lat == null || u.lon == null) {
          /* kein km-Limit */
        } else if (o.lat == null || o.lon == null) {
          return false;
        } else {
          const km = helpers.haversineKm(u.lat, u.lon, o.lat, o.lon);
          if (km > filters.km) return false;
        }
        return true;
      })
      .map(o => {
        let km = null;
        if (u.lat != null && o.lat != null) km = helpers.haversineKm(u.lat, u.lon, o.lat, o.lon);
        return { o, km };
      })
      .sort((a, b) => (a.km ?? 9999) - (b.km ?? 9999))
      .filter(({ o }) => !liked.includes(o.id));
  },

  getIncomingLikes() {
    const u = this.myProfile;
    return this._profiles.filter(
      o =>
        o.id !== u.id &&
        !o.demo &&
        !(o.id && String(o.id).startsWith('demo_')) &&
        this.theyLikedMe(o.id) &&
        !this.myBlockIds().includes(o.id)
    );
  },

  threadKey(a, b) {
    return [a, b].sort().join('_');
  },

  async ensureThread(otherId) {
    const key = this.threadKey(this.myProfile.id, otherId);
    if (!this._threads) this._threads = {};
    if (this._threads[key]) return this._threads[key];
    if (this.mode === 'cloudflare') {
      try {
        const j = await this._cloudFetch('chats/' + encodeURIComponent(otherId) + '/messages');
        const t = { thread_key: key, msg_count: j.msgCount || 0 };
        this._threads[key] = t;
        return t;
      } catch (e) {
        const t = { thread_key: key, msg_count: 0 };
        this._threads[key] = t;
        return t;
      }
    }
    if (!this.sb) throw new Error('Chat-Server nicht verbunden');
    const { data } = await this.sb
      .from('chat_threads')
      .select('*')
      .eq('thread_key', key)
      .maybeSingle();
    if (data) {
      this._threads[key] = data;
      return data;
    }
    const ids = [this.myProfile.id, otherId].sort();
    const { data: created, error } = await this.sb
      .from('chat_threads')
      .insert({
        thread_key: key,
        profile_a: ids[0],
        profile_b: ids[1],
        msg_count: 0,
      })
      .select()
      .single();
    if (error) throw error;
    this._threads[key] = created;
    return created;
  },

  async fetchMessages(otherId) {
    const thread = await this.ensureThread(otherId);
    const { data } = await this.sb
      .from('messages')
      .select('*')
      .eq('thread_key', thread.thread_key)
      .order('created_at', { ascending: true });
    return { thread, messages: data || [] };
  },

  async sendMessage(otherId, text) {
    const thread = await this.ensureThread(otherId);
    const next = thread.msg_count + 1;
    await this.sb.from('messages').insert({
      thread_key: thread.thread_key,
      from_profile: this.myProfile.id,
      body: text,
    });
    await this.sb
      .from('chat_threads')
      .update({ msg_count: next, updated_at: new Date().toISOString() })
      .eq('thread_key', thread.thread_key);
    thread.msg_count = next;
    this._threads[thread.thread_key] = thread;
    return next;
  },

  _stripeEnabled() {
    const cfg = this._stripeCfg || {};
    const pk = String(cfg.publishableKey || '').trim();
    if (!cfg.enabled || !pk.startsWith('pk_')) return false;
    if (/HIER|\.\.\.|DEIN/i.test(pk)) return false;
    return pk.length > 20;
  },

  _stripeCfgPromise: null,

  _loadSupabaseJs() {
    if (window.supabase) return Promise.resolve(window.supabase);
    return new Promise((resolve, reject) => {
      const existing = document.querySelector('script[data-jami-supabase]');
      if (existing) {
        existing.addEventListener('load', () => resolve(window.supabase));
        existing.addEventListener('error', reject);
        return;
      }
      const s = document.createElement('script');
      s.src = 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2';
      s.async = true;
      s.dataset.jamiSupabase = '1';
      s.onload = () => resolve(window.supabase);
      s.onerror = reject;
      document.head.appendChild(s);
    });
  },

  async loadStripeConfig() {
    try {
      const r = await fetch('js/stripe-config.json');
      if (r.ok) this._stripeCfg = await r.json();
    } catch (e) {}
    this._stripeCfg = this._stripeCfg || { enabled: false };
    return this._stripeCfg;
  },

  async ensureStripeConfig() {
    if (this._stripeCfg) return this._stripeCfg;
    if (!this._stripeCfgPromise) {
      this._stripeCfgPromise = this.loadStripeConfig();
    }
    return this._stripeCfgPromise;
  },

  async refreshMyProfile() {
    if (this.mode === 'cloudflare') return this._cloudLoadMe();
    if (!this.myProfile || !this.sb) return;
    const { data } = await this.sb.from('profiles').select('*').eq('id', this.myProfile.id).single();
    if (data) this.myProfile = this.profileFromRow(data);
    return this.myProfile;
  },

  async fetchMessagesCloud(otherId) {
    const j = await this._cloudFetch('chats/' + otherId + '/messages');
    return {
      thread: { msg_count: j.msgCount },
      msgCount: j.msgCount,
      isMutualMatch: !!j.isMutualMatch,
      freeMessagesLeft: j.freeMessagesLeft ?? 0,
      chatStatus: j.chatStatus || null,
      messages: (j.messages || []).map(m => ({
        from_profile: m.from,
        body: m.text,
        at: m.at,
        read: !!m.read,
      })),
    };
  },

  async fetchChatStatusCloud(otherId) {
    return this._cloudFetch('chats/' + encodeURIComponent(otherId) + '/status');
  },

  async markChatReadCloud(otherId) {
    return this._cloudFetch('chats/' + encodeURIComponent(otherId) + '/read', {
      method: 'POST',
      body: '{}',
    });
  },

  async sendChatTypingCloud(otherId, typing) {
    return this._cloudFetch('chats/' + encodeURIComponent(otherId) + '/typing', {
      method: 'POST',
      body: JSON.stringify({ typing: !!typing }),
    });
  },

  async pushSubscribe(subscription) {
    return this._cloudFetch('push/subscribe', {
      method: 'POST',
      body: JSON.stringify(subscription),
    });
  },

  async pushUnsubscribe(endpoint) {
    return this._cloudFetch('push/unsubscribe', {
      method: 'POST',
      body: JSON.stringify({ endpoint: endpoint || '' }),
    });
  },

  async sendMessageCloud(otherId, text) {
    const j = await this._cloudFetch('chats/' + otherId + '/messages', {
      method: 'POST',
      body: JSON.stringify({ text }),
    });
    if (j.coins != null) this.myProfile.coins = j.coins;
    if (j.activityBadges && this.myProfile) this.myProfile.activityBadges = j.activityBadges;
    return j;
  },

  async fetchInboxSummaryCloud() {
    const j = await this._cloudFetch('notifications/summary');
    this._inboxSummary = j;
    return j;
  },

  async listChatPartnersCloud() {
    const j = await this._cloudFetch('chats');
    return (j.items || []).map(x => ({
      otherId: x.otherId,
      other: x.profile,
      msgCount: x.msgCount,
      unreadCount: x.unreadCount || 0,
    }));
  },

  _stripePriceForPack(pack) {
    const id = pack?.id || pack?.packId || '';
    return (pack?.stripePriceId || this._stripeCfg?.priceIds?.[id] || '').trim();
  },

  _loadStripeJs() {
    return new Promise((resolve, reject) => {
      if (typeof window.Stripe === 'function') {
        resolve(window.Stripe);
        return;
      }
      const existing = document.querySelector('script[data-jami-stripe]');
      if (existing) {
        existing.addEventListener('load', () => resolve(window.Stripe));
        existing.addEventListener('error', reject);
        return;
      }
      const s = document.createElement('script');
      s.src = 'https://js.stripe.com/v3/';
      s.async = true;
      s.dataset.jamiStripe = '1';
      s.onload = () => resolve(window.Stripe);
      s.onerror = () => reject(new Error('Stripe.js konnte nicht geladen werden'));
      document.head.appendChild(s);
    });
  },

  async _redirectStripeCheckout(sessionId, url) {
    if (url) {
      const inApp = document.documentElement.classList.contains('jami-lite');
      if (inApp) {
        const opened = window.open(url, '_blank', 'noopener,noreferrer');
        if (!opened) {
          return { ok: false, error: 'Popup blockiert – bitte in Chrome/Safari öffnen: ' + url, url };
        }
        return { ok: true, url };
      }
      window.location.assign(url);
      return { ok: true, url };
    }
    const pk = this._stripeCfg?.publishableKey;
    if (sessionId && pk) {
      try {
        const StripeCtor = await this._loadStripeJs();
        const stripe = StripeCtor(pk);
        const { error } = await stripe.redirectToCheckout({ sessionId });
        if (error) return { ok: false, error: error.message };
        return { ok: true };
      } catch (e) {
        return { ok: false, error: e.message || 'Stripe-Redirect fehlgeschlagen' };
      }
    }
    return { ok: false, error: 'Keine Checkout-URL von Stripe' };
  },

  async confirmStripeCheckout(sessionId) {
    if (!this.myProfile) return { ok: false, error: 'Bitte einloggen' };
    const sid = String(sessionId || '').trim();
    if (!sid) return { ok: false, error: 'Keine Session' };
    if (this.mode !== 'cloudflare') return { ok: false, error: 'Nur Cloudflare' };
    try {
      const j = await this._cloudFetch('confirm-checkout-session', {
        method: 'POST',
        body: JSON.stringify({ sessionId: sid }),
      });
      if (j.profile) this.myProfile = j.profile;
      return { ok: true, credited: j.credited || 0, duplicate: !!j.duplicate };
    } catch (e) {
      return { ok: false, error: e.message || 'Bestätigung fehlgeschlagen' };
    }
  },

  async startStripeCheckout(pack, options) {
    options = options || {};
    if (!this.myProfile) return { ok: false, error: 'Bitte einloggen' };
    await this.ensureStripeConfig();
    if (!this._stripeEnabled()) return { ok: false, error: 'Stripe nicht aktiv (stripe-config.json)' };

    const priceId = this._stripePriceForPack(pack);
    const payWith = String(options.payWith || 'all').trim() || 'all';
    const payload = {
      userId: this.myProfile.id,
      profileId: this.myProfile.id,
      priceId: priceId || undefined,
      packId: pack.id,
      coins: pack.coins ?? pack.baseCoins,
      priceEur: pack.price,
      label: pack.label || 'Coins',
      payWith: payWith,
    };

    if (this.mode === 'cloudflare') {
      try {
        const j = await this._cloudFetch('create-checkout-session', {
          method: 'POST',
          body: JSON.stringify(payload),
        });
        return await this._redirectStripeCheckout(j.sessionId, j.url);
      } catch (e) {
        return { ok: false, error: e.message || 'Checkout fehlgeschlagen' };
      }
    }

    if (!this.sb) {
      return { ok: false, error: 'Bitte einloggen (Supabase)' };
    }
    const { data, error } = await this.sb.functions.invoke('create-checkout', {
      body: {
        profile_id: this.myProfile.id,
        coins: payload.coins,
        price_eur: pack.price,
        label: pack.label || 'Coins',
        success_base: window.location.origin + window.location.pathname.replace(/\/$/, '') || window.location.origin,
      },
    });
    if (error) return { ok: false, error: error.message || 'Checkout nicht erreichbar' };
    if (data?.error) return { ok: false, error: data.error };
    return this._redirectStripeCheckout(data?.sessionId, data?.url);
  },

  async fetchFriendRequests() {
    if (this.mode === 'cloudflare') return this._cloudFetch('friend-requests');
    return { incoming: [], outgoing: [], friends: [] };
  },

  async sendFriendRequest(toId) {
    if (this.mode === 'cloudflare') {
      return this._cloudFetch('friend-request', { method: 'POST', body: JSON.stringify({ toId }) });
    }
    return { ok: false, error: 'Nur mit Server-Login' };
  },

  async respondFriendRequest(fromId, accept) {
    if (this.mode === 'cloudflare') {
      return this._cloudFetch('friend-request/respond', {
        method: 'POST',
        body: JSON.stringify({ fromId, accept }),
      });
    }
    return { ok: false, error: 'Nur mit Server-Login' };
  },

  async getAdRewardStatus() {
    if (this.mode === 'cloudflare') return this._cloudFetch('watch-ad');
    return null;
  },

  async startAdWatch() {
    if (this.mode === 'cloudflare') {
      return this._cloudFetch('watch-ad/start', { method: 'POST', body: '{}' });
    }
    return { ok: false, error: 'Nur mit Server-Login' };
  },

  async completeAdWatch(token) {
    if (this.mode === 'cloudflare') {
      const j = await this._cloudFetch('watch-ad/complete', {
        method: 'POST',
        body: JSON.stringify({ token }),
      });
      if (j.coins != null && this.myProfile) this.myProfile.coins = j.coins;
      return j;
    }
    return { ok: false, error: 'Nur mit Server-Login' };
  },

  async listChatPartners() {
    const pid = this.myProfile.id;
    const { data } = await this.sb
      .from('chat_threads')
      .select('*')
      .or(`profile_a.eq.${pid},profile_b.eq.${pid}`);
    return (data || []).map(t => {
      const otherId = t.profile_a === pid ? t.profile_b : t.profile_a;
      const other = this._profiles.find(p => p.id === otherId);
      return { thread: t, otherId, other, msgCount: t.msg_count };
    }).filter(x => x.other);
  },
};
