// estado.jsx — estado global do app (localStorage) + hash router de O Casal Legendário.
const StoreCtx = createContext(null);
const KEY = 'ocl_state_v1';
const UID_KEY = 'ocl_usuario_id';

// ── Sync com a API (Next/Prisma/Postgres) ─────────────────────────────────
// ADITIVO e GRACEFUL: o localStorage continua sendo a fonte de verdade (o app
// funciona offline). Estas chamadas são fire-and-forget; se a API estiver fora
// (ex.: demo público sem túnel da API), o .catch engole o erro e nada quebra.
const API_BASE = (typeof window !== 'undefined' && window.OCL_API) || 'http://localhost:3001';
function getUsuarioId() { try { return localStorage.getItem(UID_KEY); } catch (e) { return null; } }

// Re-onboarding gracioso: se a API responder 401 (token ausente/cru-antigo/forjado),
// limpamos o id local e recriamos a identidade a partir do state salvo, obtendo um
// token assinado novo. Mantém offline-first: tudo é fire-and-forget; se a API
// estiver fora, o .catch engole e o localStorage segue como fonte de verdade.
let reonboardando = false;
async function reonboard() {
  if (reonboardando) return null;
  reonboardando = true;
  try {
    try { localStorage.removeItem(UID_KEY); } catch (e) {}
    let s = {};
    try { s = JSON.parse(localStorage.getItem(KEY) || '{}'); } catch (e) {}
    if (!s || !s.onboarded || !s.name) return null; // sem dados → onboarding normal cuidará
    const r = await fetch(API_BASE + '/api/identidade', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ nome: s.name || 'amado', persona: s.persona, parceiroNome: s.partnerName, coupleMode: s.coupleMode }),
    });
    if (!r.ok) return null;
    const res = await r.json();
    if (res && res.id) { try { localStorage.setItem(UID_KEY, res.id); } catch (e) {} return res.id; }
    return null;
  } catch (e) { return null; } finally { reonboardando = false; }
}

function apiCall(path, body) {
  const uid = getUsuarioId();
  const headers = { 'Content-Type': 'application/json' };
  if (uid) headers['x-usuario-id'] = uid;
  return fetch(API_BASE + path, {
    method: body ? 'POST' : 'GET',
    headers,
    body: body ? JSON.stringify(body) : undefined,
  }).then(async (r) => {
    if (r.ok) return r.json();
    // 401 com um uid presente = token inválido/antigo → re-onboarda e repete UMA vez.
    if (r.status === 401 && uid) {
      const novo = await reonboard();
      if (novo && path !== '/api/identidade') {
        const h2 = { 'Content-Type': 'application/json', 'x-usuario-id': novo };
        const r2 = await fetch(API_BASE + path, {
          method: body ? 'POST' : 'GET', headers: h2,
          body: body ? JSON.stringify(body) : undefined,
        });
        return r2.ok ? r2.json() : null;
      }
    }
    return null;
  }).catch(() => null);
}

// Web Push: pede permissão, registra o SW, assina e manda a inscrição pra API.
function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const raw = atob(base64);
  const arr = new Uint8Array(raw.length);
  for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i);
  return arr;
}
function isIosLike() {
  const ua = navigator.userAgent || '';
  return /iPad|iPhone|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
}
function isPwaStandalone() {
  return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
}
function motivoPushSemSuporte() {
  if (isIosLike() && !isPwaStandalone()) return 'No iPhone, instale o app na tela inicial para ativar notificações.';
  if (!window.isSecureContext) return 'Notificações precisam de HTTPS.';
  return 'Este navegador não suporta Web Push.';
}
function normalizarErroPush(e) {
  const msg = String(e || '');
  if (/push service not available|permission denied|incognito/i.test(msg)) {
    return 'O navegador bloqueou o serviço de push. Teste fora do modo privado e com notificações permitidas.';
  }
  return msg || 'Não foi possível ativar notificações.';
}
async function serializarInscricao(reg, publicKey) {
  let sub = await reg.pushManager.getSubscription();
  if (!sub) {
    sub = await reg.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: urlBase64ToUint8Array(publicKey),
    });
  }
  const j = sub.toJSON();
  return { endpoint: j.endpoint, keys: j.keys };
}
async function ativarPush() {
  try {
    if (!('Notification' in window) || !('serviceWorker' in navigator) || !('PushManager' in window)) {
      return { ok: false, motivo: motivoPushSemSuporte() };
    }
    const uid = getUsuarioId() || await reonboard();
    if (!uid) return { ok: false, motivo: 'Finalize o onboarding antes de ativar notificações.' };
    const perm = await Notification.requestPermission();
    if (perm !== 'granted') return { ok: false, motivo: 'Permissão de notificação negada no navegador.' };
    const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/', updateViaCache: 'none' });
    await navigator.serviceWorker.ready;
    const chave = await apiCall('/api/push/chave');
    if (!chave || !chave.publicKey) return { ok: false, motivo: 'API/chave indisponível' };
    const sub = await serializarInscricao(reg, chave.publicKey);
    const salvo = await apiCall('/api/push/inscrever', sub);
    if (!salvo || !salvo.ok) return { ok: false, motivo: 'Não consegui salvar este dispositivo na API.' };
    return { ok: true, endpoint: sub.endpoint };
  } catch (e) { return { ok: false, motivo: normalizarErroPush(e) }; }
}
async function testarPush() {
  const r = await apiCall('/api/push/testar', {
    titulo: 'O Casal Legendário',
    corpo: 'Teste recebido. Seu lembrete diário está pronto.',
    url: '/#/home',
  });
  if (!r) return { ok: false, motivo: 'API indisponível para o teste.' };
  if (!r.ok) return { ok: false, motivo: r.aviso || 'Nenhuma notificação foi entregue.', detalhes: r };
  return { ok: true, enviados: r.enviados || 0 };
}

const todayStr = () => new Date().toISOString().slice(0, 10);
function daysBetween(a, b) {
  return Math.round((new Date(b) - new Date(a)) / 86400000);
}
function computeStreak(dates) {
  if (!dates || !dates.length) return 0;
  const set = new Set(dates);
  let streak = 0;
  let cur = new Date();
  // allow streak to count if today OR yesterday is the last done day
  const t = todayStr();
  const y = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
  if (!set.has(t) && !set.has(y)) return 0;
  if (!set.has(t)) cur = new Date(Date.now() - 86400000);
  while (set.has(cur.toISOString().slice(0, 10))) {
    streak++;
    cur = new Date(cur.getTime() - 86400000);
  }
  return streak;
}

const DEFAULT_STATE = {
  onboarded: false,
  name: '',
  persona: null,
  coupleMode: false,
  partnerName: '',
  doneDates: [],            // devotional/day completion dates
  completed: {},            // { journeyId: [1,2,...] }
  favPrayers: [],
  favDevos: [],
  unlocked: [],             // product ids
  sensitiveAck: [],         // journey ids acknowledged
  reminderTime: '20:30',
  reminderOn: true,
  testimonials: [],         // user-added
};

function loadState() {
  try {
    const raw = localStorage.getItem(KEY);
    if (raw) return { ...DEFAULT_STATE, ...JSON.parse(raw) };
  } catch (e) {}
  return { ...DEFAULT_STATE };
}

function StoreProvider({ children }) {
  const [state, setState] = useState(loadState);
  useEffect(() => {
    try { localStorage.setItem(KEY, JSON.stringify(state)); } catch (e) {}
  }, [state]);

  // Sync real entre dispositivos: hidrata progresso/streak do servidor no load.
  // ADITIVO (união, nunca remove dado local) e offline-first (API fora = .catch
  // engole e o localStorage segue como fonte de verdade). Mesma conta (por email)
  // num novo aparelho → onboarding recupera o usuário → aqui o progresso volta.
  useEffect(() => {
    if (!getUsuarioId()) return;
    apiCall('/api/progresso').then((res) => {
      if (!res) return;
      setState((s) => {
        const doneDates = Array.from(new Set([...(s.doneDates || []), ...((res.diasConcluidos) || [])])).sort();
        const completed = { ...(s.completed || {}) };
        const jornadas = res.jornadas || {};
        for (const jid of Object.keys(jornadas)) {
          const merged = new Set([...(completed[jid] || []), ...((jornadas[jid]) || [])]);
          completed[jid] = Array.from(merged).sort((a, b) => a - b);
        }
        return { ...s, doneDates, completed };
      });
    });
  }, []);

  const up = (patch) => setState((s) => ({ ...s, ...(typeof patch === 'function' ? patch(s) : patch) }));

  const actions = {
    setName: (name) => up({ name }),
    setPersona: (persona) => up({ persona }),
    finishOnboarding: (data) => {
      up({ onboarded: true, ...data });
      apiCall('/api/identidade', { nome: data.name || 'amado', persona: data.persona, parceiroNome: data.partnerName, coupleMode: data.coupleMode })
        .then((res) => { if (res && res.id) { try { localStorage.setItem(UID_KEY, res.id); } catch (e) {} } });
    },
    resetAll: () => { setState({ ...DEFAULT_STATE }); },

    markToday: () => {
      up((s) => s.doneDates.includes(todayStr()) ? s : { doneDates: [...s.doneDates, todayStr()] });
      apiCall('/api/progresso', { acao: 'devocional' });
    },

    completeDay: (jid, n) => {
      up((s) => {
        const arr = s.completed[jid] || [];
        const completed = arr.includes(n) ? s.completed : { ...s.completed, [jid]: [...arr, n].sort((a, b) => a - b) };
        const doneDates = s.doneDates.includes(todayStr()) ? s.doneDates : [...s.doneDates, todayStr()];
        return { completed, doneDates };
      });
      apiCall('/api/progresso', { acao: 'jornada', jornadaId: jid, dia: n });
      apiCall('/api/progresso', { acao: 'devocional' });
    },

    toggleFavPrayer: (id) => up((s) => ({ favPrayers: s.favPrayers.includes(id) ? s.favPrayers.filter(x => x !== id) : [...s.favPrayers, id] })),
    toggleFavDevo: (id) => up((s) => ({ favDevos: s.favDevos.includes(id) ? s.favDevos.filter(x => x !== id) : [...s.favDevos, id] })),

    unlock: (pid) => up((s) => s.unlocked.includes(pid) ? s : { unlocked: [...s.unlocked, pid] }),
    ackSensitive: (jid) => up((s) => s.unlocked.includes(jid) ? s : { sensitiveAck: [...new Set([...s.sensitiveAck, jid])] }),

    setReminder: (reminderTime) => up({ reminderTime }),
    setReminderOn: (reminderOn) => up({ reminderOn }),
    toggleReminder: () => up((s) => ({ reminderOn: !s.reminderOn })),
    setCoupleMode: (coupleMode) => up({ coupleMode }),
    setPartnerName: (partnerName) => up({ partnerName }),

    addTestimonial: (t) => {
      up((s) => ({ testimonials: [{ ...t, id: 'u' + Date.now() }, ...s.testimonials] }));
      apiCall('/api/comunidade', { nome: t.name || t.nome || 'Anônimo', cidade: t.city || t.cidade, texto: t.text || t.texto || '', anon: t.anon });
    },
  };

  // derived
  const streak = computeStreak(state.doneDates);
  const journeyProgress = (jid) => (state.completed[jid] || []).length;
  const isDayDone = (jid, n) => (state.completed[jid] || []).includes(n);
  const isUnlocked = (pid) => state.unlocked.includes(pid);
  const isSensitiveOk = (jid) => state.sensitiveAck.includes(jid);
  const doneToday = state.doneDates.includes(todayStr());

  const value = { state, actions, streak, journeyProgress, isDayDone, isUnlocked, isSensitiveOk, doneToday, todayStr };
  return <StoreCtx.Provider value={value}>{children}</StoreCtx.Provider>;
}
function useStore() { return useContext(StoreCtx); }

// ── hash router ──────────────────────────────────────────────
function parseHash() {
  let h = (location.hash || '#/home').replace(/^#/, '');
  if (!h.startsWith('/')) h = '/' + h;
  const parts = h.split('/').filter(Boolean);
  return { path: h, parts };
}
// título da tela atual (usa appbarTitle/NAV de aplicativo.jsx; guardado p/ ordem de carga)
function tituloRota() {
  try {
    const { parts } = parseHash();
    let t = (typeof appbarTitle === 'function' && appbarTitle(parts)) || '';
    if (!t && typeof NAV !== 'undefined') {
      const item = NAV.find(n => n.path === '/' + (parts[0] || 'home'));
      t = item ? item.label : '';
    }
    return t || 'O Casal Legendário';
  } catch (e) { return 'O Casal Legendário'; }
}
function useRoute() {
  const [route, setRoute] = useState(parseHash);
  useEffect(() => {
    const on = () => {
      setRoute(parseHash());
      window.scrollTo(0, 0);
      const sc = document.querySelector('.scroll');
      // foco programático no contêiner de conteúdo: orienta leitores de tela ao trocar de rota (sem barra de URL no standalone)
      if (sc) { sc.scrollTop = 0; sc.setAttribute('tabindex', '-1'); try { sc.focus({ preventScroll: true }); } catch (e) {} }
      const titulo = tituloRota();
      try { document.title = titulo + ' — O Casal Legendário'; } catch (e) {}
      const live = document.getElementById('rota-live');
      if (live) live.textContent = titulo; // anúncio aria-live da nova tela
    };
    window.addEventListener('hashchange', on);
    return () => window.removeEventListener('hashchange', on);
  }, []);
  return route;
}
function nav(path) {
  if (!path.startsWith('/')) path = '/' + path;
  if (location.hash === '#' + path) {
    const sc = document.querySelector('.scroll'); if (sc) sc.scrollTop = 0; return;
  }
  location.hash = path; // empilha: navegação para DETALHE permite voltar ao pai
}
// raízes (tab bar/sidebar): SUBSTITUEM a entrada atual (não empilham) → 'Voltar' nunca cai numa aba lateral
function navRoot(path) {
  if (!path.startsWith('/')) path = '/' + path;
  if (location.hash === '#' + path) {
    const sc = document.querySelector('.scroll'); if (sc) sc.scrollTop = 0; return;
  }
  try { history.replaceState(null, '', '#' + path); } catch (e) { location.hash = path; return; }
  // replaceState não dispara hashchange; o roteador escuta hashchange e lê parseHash() → forçamos
  window.dispatchEvent(new HashChangeEvent('hashchange'));
}
// PAI estrutural derivado das parts do hash (fonte única mínima de verdade da hierarquia)
function parentPath(parts) {
  parts = parts || parseHash().parts;
  const a = parts[0], b = parts[1], c = parts[2];
  if (a === 'jornada' && c === 'dia') return '/jornada/' + b; // dia → detalhe da jornada
  if (a === 'jornada') return '/jornadas';                    // detalhe → lista
  if (a === 'roteiro') return '/orar/roteiros';               // roteiro → lista de roteiros
  if (a === 'orar' && b === 'roteiros') return '/orar';       // sub-aba → orar
  if (a === 'produto') return '/biblioteca';
  if (a === 'compra') return '/biblioteca';
  if (a === 'hoje') return '/home';
  if (a === 'loja' || a === 'videos' || a === 'comunidade' || a === 'perfil') return '/home';
  return null; // telas raiz: não se aplica
}
function back() {
  const parent = parentPath(parseHash().parts);
  if (parent) { nav(parent); return; }                        // 1) pai estrutural determinista
  if (history.length > 1) history.back(); else nav('/home');  // 2) fallback temporal
}

Object.assign(window, { StoreProvider, useStore, useRoute, nav, navRoot, back, parentPath, ativarPush, testarPush });
