// integration.jsx — backend connectivity adapter without changing visual design.
// Loaded after design components and before the router. It exposes data + actions only.
(function () {
  const parentConfig = (() => {
    try { return window.parent && window.parent !== window ? window.parent.__FITBODY_CONFIG__ : null; } catch (e) { return null; }
  })();
  const env = window.FITBODY_ENV || window.__FITBODY_CONFIG__ || parentConfig || {};
  const API_BASE = pick(env.API_BASE, env.apiBaseUrl, safeLocal('fb_api_base'), '');
  const CONTENT_BASE = pick(env.CONTENT_BASE, env.contentBaseUrl, safeLocal('fb_content_base'), API_BASE);
  const ANALYTICS_BASE = pick(env.ANALYTICS_BASE, env.analyticsBaseUrl, safeLocal('fb_analytics_base'), API_BASE);
  const REALTIME_URL = pick(env.REALTIME_URL, env.realtimeUrl, safeLocal('fb_realtime_url'), realtimeFromLocation());

  const state = {
    ready: false,
    offline: false,
    errors: [],
    me: null,
    profile: null,
    preferences: null,
    workouts: [],
    sessions: [],
    recommendations: [],
    plans: [],
    subscription: null,
    leaderboard: null,
    notifications: [],
    activeSessions: {},
  };

  function pick() {
    for (let i = 0; i < arguments.length; i++) {
      if (arguments[i] !== undefined && arguments[i] !== null && arguments[i] !== '') return arguments[i];
    }
    return '';
  }

  function safeLocal(key) {
    try { return localStorage.getItem(key) || ''; } catch (e) { return ''; }
  }

  function safeSetLocal(key, value) {
    try { localStorage.setItem(key, value); } catch (e) {}
  }

  function realtimeFromLocation() {
    if (!window.location || !window.location.host) return '';
    const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    return `${scheme}//${window.location.host}/v1/realtime`;
  }

  function url(base, path) {
    if (!base) return path;
    return `${base.replace(/\/$/, '')}${path}`;
  }

  function getToken() {
    return safeLocal('fb_access_token') || safeLocal('fitbody_access_token') || env.devAccessToken || '';
  }

  function setTokens(payload) {
    const access = payload && (payload.accessToken || payload.access_token);
    const refresh = payload && (payload.refreshToken || payload.refresh_token);
    if (access) safeSetLocal('fb_access_token', access);
    if (refresh) safeSetLocal('fb_refresh_token', refresh);
  }

  function headers(extra, method) {
    const h = { Accept: 'application/json', 'X-Request-Id': idempotencyKey('req'), ...(extra || {}) };
    const token = getToken();
    if (token) h.Authorization = `Bearer ${token}`;
    if (method && method !== 'GET' && method !== 'HEAD') {
      if (!h['Content-Type']) h['Content-Type'] = 'application/json';
      if (!h['Idempotency-Key']) h['Idempotency-Key'] = idempotencyKey('idem');
    }
    return h;
  }

  function idempotencyKey(prefix) {
    return `${prefix}-${ulid()}`;
  }

  const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
  function ulid() {
    let time = Date.now();
    let out = '';
    for (let i = 9; i >= 0; i--) {
      out = CROCKFORD[time % 32] + out;
      time = Math.floor(time / 32);
    }
    const bytes = new Uint8Array(16);
    if (window.crypto && window.crypto.getRandomValues) window.crypto.getRandomValues(bytes);
    else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
    for (let i = 0; i < 16; i++) out += CROCKFORD[bytes[i] & 31];
    return out;
  }

  async function request(base, path, options) {
    const method = (options && options.method) || 'GET';
    const response = await fetch(url(base, path), {
      ...options,
      method,
      headers: headers(options && options.headers, method),
    });
    const text = await response.text();
    if (!response.ok) {
      const error = new Error(`API ${method} ${path} failed: ${response.status}`);
      error.status = response.status;
      error.body = text;
      throw error;
    }
    return text ? JSON.parse(text) : null;
  }

  async function optional(base, path, fallback, options) {
    try {
      return await request(base, path, options);
    } catch (error) {
      if (!error.status || error.status >= 500) state.offline = true;
      state.errors.push({ path, status: error.status || 0 });
      return fallback;
    }
  }

  async function ensureToken() {
    if (getToken()) return getToken();
    const code = env.devOAuthCode || '';
    if (!code) return '';
    try {
      const payload = await request(API_BASE, '/v1/auth/oauth/google/callback', {
        method: 'POST',
        body: JSON.stringify({ code, device_name: 'fitbody-mobile-web' }),
      });
      setTokens(payload);
      return getToken();
    } catch (error) {
      state.errors.push({ path: '/v1/auth/oauth/google/callback', status: error.status || 0 });
      return '';
    }
  }

  function asArray(value, keys) {
    if (Array.isArray(value)) return value;
    if (!value) return [];
    for (let i = 0; i < keys.length; i++) {
      if (Array.isArray(value[keys[i]])) return value[keys[i]];
    }
    return [];
  }

  async function bootstrap() {
    state.offline = false;
    state.errors = [];
    await ensureToken();

    let [me, profile, preferences, workouts, sessions, recommendations, plans, subscription, notifications, leaderboard] = await Promise.all([
      optional(API_BASE, '/v1/me', null),
      optional(API_BASE, '/v1/me/profile', null),
      optional(API_BASE, '/v1/me/preferences', null),
      optional(API_BASE, '/v1/me/workouts', null),
      optional(API_BASE, '/v1/me/sessions', null),
      optional(CONTENT_BASE, '/v1/catalog/recommendations?context=today', null),
      optional(API_BASE, '/v1/billing/plans', []),
      optional(API_BASE, '/v1/billing/me/subscription', null),
      optional(API_BASE, '/v1/me/notifications', []),
      optional(API_BASE, '/v1/leaderboard/weekly', null),
    ]);

    if (getToken() && asArray(workouts, ['workouts', 'items', 'sessions']).length === 0) {
      await optional(API_BASE, '/v1/scheduler/plans:generate', null, {
        method: 'POST',
        body: JSON.stringify({ goal: 'daily_health_action', level: 'beginner', days_per_week: 5, available_minutes: 2, equipment: [] }),
      });
      workouts = await optional(API_BASE, '/v1/me/workouts', workouts);
    }

    state.me = me;
    state.profile = profile;
    state.preferences = preferences;
    state.workouts = asArray(workouts, ['workouts', 'items', 'sessions']);
    state.sessions = asArray(sessions, ['sessions', 'items']);
    state.recommendations = asArray(recommendations, ['items', 'recommendations', 'videos']);
    state.plans = asArray(plans, ['plans', 'items']);
    state.subscription = subscription;
    state.leaderboard = leaderboard;
    state.notifications = asArray(notifications, ['notifications', 'items']);
    state.ready = true;
    return snapshot();
  }

  function questFromWorkout(workout) {
    if (!workout) return null;
    const exercise = workout.exercise || workout.family || 'mobility';
    const seconds = Math.max(10, Math.min(120, workout.hold_seconds || (workout.minutes ? workout.minutes * 60 : 30)));
    const title = workout.name || exercise || '오늘의 Quest';
    return {
      id: workout.id,
      workout_id: workout.id,
      title,
      seconds,
      xp: seconds <= 20 ? 8 : 10,
      meta: `${seconds}초 · ${workout.mode || '앉아서 가능'}`,
      cat: categoryFor(exercise),
      icon: iconFor(exercise),
      tone: toneFor(exercise),
      steps: stepsFor(exercise),
      avaLine: '오늘 컨디션에 맞춰 짧게 완료해요',
    };
  }

  function questFromRecommendation(item) {
    const source = item && (item.video || item.workout || item.quest || item);
    if (!source) return null;
    const seconds = Math.max(10, Math.min(120, source.duration_sec || source.durationSec || source.seconds || 30));
    const title = source.title || source.name || '추천 Quest';
    return {
      id: source.id || source.video_id || source.videoId || title,
      workout_id: source.workout_id || source.workoutId || source.id,
      title,
      seconds,
      xp: seconds <= 20 ? 8 : 10,
      meta: `${seconds}초 · 추천`,
      cat: 'mobility',
      icon: 'wind',
      steps: source.steps || ['편하게 준비해요', '천천히 움직여요', '길게 호흡해요'],
      avaLine: '완료 가능한 작은 행동부터 시작해요',
    };
  }

  function categoryFor(exercise) {
    const e = String(exercise || '').toLowerCase();
    if (e.includes('breath') || e.includes('recovery')) return 'recovery';
    if (e.includes('squat') || e.includes('core') || e.includes('strength')) return 'strength';
    if (e.includes('walk') || e.includes('cardio')) return 'cardio';
    return 'mobility';
  }

  function iconFor(exercise) {
    const cat = categoryFor(exercise);
    if (cat === 'recovery') return 'leaf';
    if (cat === 'strength') return 'dumbbell';
    if (cat === 'cardio') return 'bolt';
    return 'wind';
  }

  function toneFor(exercise) {
    const cat = categoryFor(exercise);
    if (cat === 'recovery') return T.calm;
    if (cat === 'strength') return T.violet;
    if (cat === 'cardio') return T.teal;
    return T.coral;
  }

  function stepsFor(exercise) {
    const e = String(exercise || '').toLowerCase();
    if (e.includes('breath') || e.includes('recovery')) return ['편하게 자세를 잡아요', '천천히 들이마셔요', '길게 내쉬어요'];
    if (e.includes('squat')) return ['발을 안정적으로 놓아요', '엉덩이를 뒤로 보내요', '천천히 올라와요'];
    if (e.includes('core')) return ['등을 곧게 세워요', '배에 힘을 줘요', '천천히 호흡해요'];
    return ['어깨를 부드럽게 올려요', '뒤로 천천히 돌려요', '숨을 길게 내쉬어요'];
  }

  function normalizeUser(me, profile, subscription) {
    const displayName = pick(profile && (profile.display_name || profile.displayName), me && (me.name || me.display_name || me.displayName), '지민');
    const first = String(displayName).trim().charAt(0) || '지';
    const tier = subscription && (subscription.tier || subscription.plan || subscription.plan_id || subscription.planId || subscription.status);
    return {
      displayName,
      initial: first,
      pro: Boolean(subscription && tier && !String(tier).toLowerCase().includes('free')),
    };
  }

  function dataFromSnapshot(snap, current) {
    const cur = current || {};
    const user = normalizeUser(snap.me, snap.profile, snap.subscription);
    const finished = (snap.sessions || []).filter(s => String(s.status || '').toLowerCase() === 'finished' || s.finished_at || s.finishedAt);
    const today = new Date().toISOString().slice(0, 10);
    const completedToday = finished.some(s => String(s.finished_at || s.finishedAt || s.started_at || s.startedAt || '').slice(0, 10) === today);
    const sourceQuest = questFromWorkout((snap.workouts || []).find(w => String(w.status || '').toLowerCase() !== 'completed') || (snap.workouts || [])[0]) || questFromRecommendation((snap.recommendations || [])[0]);
    const stations = buildStations(sourceQuest, completedToday || cur.completedToday);
    const xpFromSessions = finished.length ? finished.length * 10 : cur.xp;
    return {
      ...cur,
      ...user,
      completedToday: completedToday || cur.completedToday,
      homeVariant: (completedToday || cur.completedToday) ? 'completed' : (cur.homeVariant || 'default'),
      xp: xpFromSessions || cur.xp,
      bestStreak: Math.max(cur.bestStreak || 6, cur.streak || 0),
      todayQuest: sourceQuest || cur.todayQuest,
      questStations: stations || cur.questStations,
      notificationCount: (snap.notifications || []).filter(n => !n.read_at && !n.readAt).length,
      leaderboard: snap.leaderboard || cur.leaderboard,
      backendReady: Boolean(snap.ready),
      backendOffline: Boolean(snap.offline),
    };
  }

  function buildStations(todayQuest, done) {
    if (!todayQuest) return null;
    return [
      { id: 'today', cat: todayQuest.cat || 'mobility', icon: todayQuest.icon || 'wind', title: todayQuest.title, meta: todayQuest.meta || `${todayQuest.seconds || 30}초`, state: done ? 'done' : 'active', quest: todayQuest },
      { id: 'core', cat: 'strength', icon: 'dumbbell', title: '코어 세우기', meta: '20초 · 앉아서', state: done ? 'active' : 'locked', quest: { title: '코어 세우기', seconds: 20, tone: T.violet, steps: stepsFor('core'), avaLine: '허리가 아니라 배에 힘을', xp: 12 } },
      { id: 'reward', cat: 'breath', icon: 'star', title: '보너스 상자', meta: '연속 2개 완료 시 열림', state: 'locked', reward: true },
      { id: 'recovery', cat: 'recovery', icon: 'leaf', title: '회복 호흡', meta: '30초 · 침대에서', state: 'locked', quest: { title: '회복 호흡', seconds: 30, tone: T.calm, steps: stepsFor('recovery'), avaLine: '회복도 훈련이에요', xp: 12 } },
    ];
  }

  function rowFromBackend(row) {
    return {
      rank: row.rank,
      name: row.name,
      pts: row.points || row.pts || 0,
      days: row.healthy_days || row.healthyDays || row.days || 0,
      me: Boolean(row.me),
      priv: Boolean(row.private || row.priv),
      tag: row.tag,
    };
  }

  function leaderboardRows(leaderboard) {
    return leaderboard && Array.isArray(leaderboard.rows) ? leaderboard.rows.map(rowFromBackend) : null;
  }

  function friendRows(leaderboard) {
    return leaderboard && Array.isArray(leaderboard.friends) ? leaderboard.friends.map(rowFromBackend) : null;
  }

  function scoreRows(leaderboard) {
    if (!leaderboard || !Array.isArray(leaderboard.score_breakdown)) return null;
    return leaderboard.score_breakdown.map(item => [item.label, `${item.points >= 0 ? '+' : ''}${item.points}`, item.points >= 0 ? T.coral : T.ink3]);
  }

  function squadData(leaderboard) {
    if (!leaderboard || !leaderboard.squad) return null;
    const squad = leaderboard.squad;
    return {
      name: squad.name,
      progress: squad.progress,
      goals: Array.isArray(squad.goals) ? squad.goals.map(g => [g.label, g.value, g.goal]) : null,
      members: Array.isArray(squad.members) ? squad.members.map(m => [m.name, m.status, toneByName(m.tone)]) : null,
    };
  }

  function toneByName(name) {
    if (name === 'good') return T.good;
    if (name === 'calm') return T.calm;
    if (name === 'coral') return T.coral;
    return T.ink3;
  }

  function questKey(quest) {
    return String((quest && (quest.session_id || quest.sessionId || quest.workout_id || quest.workoutId || quest.id || quest.title)) || 'quest');
  }

  async function beginQuest(quest) {
    const key = questKey(quest);
    if (state.activeSessions[key]) return state.activeSessions[key];
    const session = await request(API_BASE, '/v1/sessions', {
      method: 'POST',
      body: JSON.stringify({ workout_id: quest && (quest.workout_id || quest.workoutId || quest.id || quest.title), device_mode: 'self-browser' }),
    });
    state.activeSessions[key] = session;
    await emit('quest_started', { quest_id: key, session_id: session.session_id || session.sessionId });
    return session;
  }

  async function completeQuest(quest) {
    const q = quest || {};
    const key = questKey(q);
    const session = state.activeSessions[key] || await beginQuest(q);
    const sessionId = session.session_id || session.sessionId || session.id;
    const event = {
      eventId: ulid(),
      monotonicSeq: 1,
      sessionId,
      nonce: idempotencyKey('nonce'),
      type: 'quest_completed',
      exercise: q.title || key,
      duration_sec: q.seconds || 0,
      reps: q.selfReport ? 1 : 0,
    };
    await request(API_BASE, `/v1/sessions/${encodeURIComponent(sessionId)}/events:batch`, {
      method: 'POST',
      body: JSON.stringify({ events: [event] }),
    });
    const report = await request(API_BASE, `/v1/sessions/${encodeURIComponent(sessionId)}/finish`, {
      method: 'POST',
      body: JSON.stringify({}),
    });
    await emit('quest_completed', { quest_id: key, session_id: sessionId, xp: q.xp || 10, seconds: q.seconds || 0 });
    delete state.activeSessions[key];
    return report;
  }

  async function emit(name, payload) {
    const userID = state.me && (state.me.id || state.me.ID || state.me.user_id || state.me.userId);
    const event = {
      event_id: ulid(),
      event_name: name,
      event_version: '1.0',
      ts_ms: Date.now(),
      user_id: userID || undefined,
      anon_id: userID ? undefined : (safeLocal('fb_anon_id') || idempotencyKey('anon')),
      session_id: payload && (payload.session_id || payload.sessionId),
      source: 'web',
      privacy_class: 'P1',
      payload: payload || {},
    };
    if (event.anon_id) safeSetLocal('fb_anon_id', event.anon_id);
    try {
      return await request(ANALYTICS_BASE, '/v1/analytics/events/single', {
        method: 'POST',
        body: JSON.stringify(event),
      });
    } catch (error) {
      state.offline = true;
      state.errors.push({ path: '/v1/analytics/events/single', status: error.status || 0 });
      return null;
    }
  }

  async function subscribe(planId) {
    const out = await request(API_BASE, '/v1/billing/checkout', {
      method: 'POST',
      body: JSON.stringify({ PlanID: planId || 'pro' }),
    });
    return out && out.url;
  }

  async function markNotificationRead(id) {
    return request(API_BASE, `/v1/me/notifications/${encodeURIComponent(id)}/read`, { method: 'POST', body: JSON.stringify({}) });
  }

  async function recordBodyCheck(report) {
    const payload = {
      body_check_id: report && report.id,
      captured_at: report && report.capturedAt,
      upper_alignment: report && report.upperAlignment,
      pelvis_balance: report && report.pelvisBalance,
      readiness: report && report.readiness,
      recommendation: report && report.recommendation,
      frame: report && report.frame,
      image_stored: false,
      privacy_class: 'P1',
    };
    return emit('body_check_completed', payload);
  }

  async function recordMotionLens(report) {
    const box = report && report.movementBox;
    const payload = {
      duration_ms: report && report.durationMs,
      peak_motion: report && report.peakMotion,
      frames: report && report.frames,
      movement_box: box ? {
        x: box.x,
        y: box.y,
        width: box.width,
        height: box.height,
        active: box.active,
      } : null,
      movement_detected: !!(report && report.ok && (report.peakMotion > 2 || (box && box.active > 8))),
      privacy_class: 'P1',
    };
    return emit('motion_lens_completed', payload);
  }


  function snapshot() {
    return JSON.parse(JSON.stringify({
      ready: state.ready,
      offline: state.offline,
      errors: state.errors,
      me: state.me,
      profile: state.profile,
      preferences: state.preferences,
      workouts: state.workouts,
      sessions: state.sessions,
      recommendations: state.recommendations,
      plans: state.plans,
      subscription: state.subscription,
      leaderboard: state.leaderboard,
      notifications: state.notifications,
    }));
  }

  window.FitbodyBackend = {
    apiBase: API_BASE,
    contentBase: CONTENT_BASE,
    analyticsBase: ANALYTICS_BASE,
    realtimeUrl: REALTIME_URL,
    bootstrap,
    beginQuest,
    completeQuest,
    emit,
    subscribe,
    markNotificationRead,
    recordBodyCheck,
    recordMotionLens,
    leaderboardRows,
    friendRows,
    scoreRows,
    squadData,
    snapshot,
    normalizeUser: (me) => normalizeUser(me, null, null),
    dataFromSnapshot,
  };
})();
