// Valdebebas Life — Conexión con Supabase: sesión, carga y guardado.
// Los componentes siguen usando useStored(key); para las claves compartidas, el estado vive aquí y cada cambio se traduce en filas.
const VB_SB_URL = "https://fjfigczyzlvjsdegyddw.supabase.co";
const VB_SB_KEY = "sb_publishable_fDt1F6-NB_fOwClUmaSglw_LHrhHwcK";
const sb = window.supabase ? window.supabase.createClient(VB_SB_URL, VB_SB_KEY, { auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true } }) : null;
const vbId = () => (window.crypto && crypto.randomUUID) ? crypto.randomUUID() : "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (c ^ (Math.random() * 16 >> c / 4)).toString(16));
const vbFileUrl = path => sb ? sb.storage.from("adjuntos").getPublicUrl(path).data.publicUrl : "";
// Fotos: se reducen a 1600 px y se guardan como JPEG (así pesan poco y se elimina la ubicación GPS de la foto)
async function vbPrepFile(file) {
  if (file.type === "application/pdf") { if (file.size > 5 * 1024 * 1024) throw new Error("El PDF «" + file.name + "» pesa más de 5 MB."); return { blob: file, name: file.name, type: file.type }; }
  if (!/^image\//.test(file.type)) throw new Error("Solo se pueden adjuntar fotos o PDF.");
  const url = URL.createObjectURL(file);
  try {
    const img = await new Promise((ok, ko) => { const i = new Image(); i.onload = () => ok(i); i.onerror = () => ko(new Error("No se pudo leer la foto «" + file.name + "».")); i.src = url; });
    const k = Math.min(1, 1600 / Math.max(img.naturalWidth, img.naturalHeight));
    const c = document.createElement("canvas"); c.width = Math.round(img.naturalWidth * k); c.height = Math.round(img.naturalHeight * k);
    c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
    const blob = await new Promise(ok => c.toBlob(ok, "image/jpeg", 0.82));
    return { blob, name: file.name.replace(/\.[^.]+$/, "") + ".jpg", type: "image/jpeg" };
  } finally { URL.revokeObjectURL(url); }
}
const vbToday = () => new Date().toISOString().slice(0, 10);

const VBStore = (() => {
  const S = { vr_me: null, vr_users: [], vr_ready: false, vr3_posts: [], vr3_social: {}, vr3_dir: [], vr3_twc: { circles: {}, moai: {}, props: [], plans: [], names: {}, emails: {} }, vr3_baro: {} };
  const SHARED = new Set(Object.keys(S));
  const subs = {};
  const emit = k => (subs[k] || []).slice().forEach(f => f());
  const put = (k, v) => { S[k] = v; emit(k); };
  const ctx = { me: null, recC: {}, voteC: {} };
  const key = id => ctx.me && id === ctx.me.id ? ctx.me.email : "u:" + id;
  let inflight = 0, loading = null, warned = false;
  const chk = r => { if (r && r.error) throw r.error; return r; };
  const rows = p => p.then(r => { if (r.error) { if (!warned) { console.warn("Supabase:", r.error.message); warned = true; } return []; } return r.data || []; }, () => []);
  const ts = s => s ? Date.parse(s) : Date.now();

  async function load() {
    if (!sb) { put("vr_ready", true); return; }
    const { data: { session } } = await sb.auth.getSession();
    const au = session && session.user;
    let me = null;
    if (au) {
      const { data: p } = await sb.from("perfiles").select("id,nombre,rol,codigo,mayor_edad").eq("id", au.id).maybeSingle();
      me = { id: au.id, email: au.email, name: p ? p.nombre : "Vecino", role: p ? p.rol : "vecino", code: p ? p.codigo : null, needsTerms: p ? !p.mayor_edad : true };
    }
    ctx.me = me;
    const [an, di, rc, vo, cm, qd, pr] = await Promise.all([
      rows(sb.from("anuncios").select("*, respuestas(id,autor_nombre,texto,creado_at)").order("creado_at", { ascending: false }).limit(500)),
      rows(sb.from("directorio").select("*")),
      rows(sb.from("recomendaciones").select("user_id,directorio_id")),
      rows(sb.from("votos_locales").select("user_id,categoria,directorio_id")),
      rows(sb.from("circulo_miembros").select("user_id,circulo")),
      rows(sb.from("quedadas").select("*, quedada_asistentes(user_id)").gte("fecha", vbToday())),
      rows(sb.from("propuestas").select("*, propuesta_votos(user_id)").order("creado_at", { ascending: false }).limit(300)),
    ]);
    let pf = [], ml = [], ba = [], co = [], em = [], stats = null;
    if (me) {
      [pf, ml, ba, co] = await Promise.all([
        rows(sb.from("perfiles").select("id,nombre,rol,codigo,invitado_por,creado_at")),
        rows(sb.from("moai_lista").select("user_id")),
        rows(sb.from("barometro").select("*").eq("user_id", me.id).order("creado_at")),
        rows(sb.from("barometro_consentimiento").select("acepta").eq("user_id", me.id)),
      ]);
      if (me.role === "admin") { em = await rows(sb.rpc("admin_emails")); const r = await sb.rpc("admin_baro_stats"); stats = r.data || null; }
      sb.rpc("tocar_actividad").then(() => {}, () => {});
    }
    const notMe = x => !me || x.user_id !== me.id;
    ctx.recC = {}; rc.filter(notMe).forEach(r => { ctx.recC[r.directorio_id] = (ctx.recC[r.directorio_id] || 0) + 1; });
    ctx.voteC = {}; vo.filter(notMe).forEach(r => { ctx.voteC[r.directorio_id] = (ctx.voteC[r.directorio_id] || 0) + 1; });
    const circles = {}; cm.forEach(m => { const k = key(m.user_id); (circles[k] = circles[k] || []).push(m.circulo); });
    window.__baroStats = stats; window.__exits = stats ? stats.bajas : 0;
    S.vr3_posts = an.map(a => ({ id: a.id, type: a.tipo, title: a.titulo, body: a.cuerpo, detail: a.detalle || "", author: a.autor_nombre || "Vecino", email: key(a.user_id), role: a.rol_autor || "vecino", files: (a.adjuntos || []).map(f => ({ ...f, url: vbFileUrl(f.path) })), ts: ts(a.creado_at), expires: ts(a.caduca_at), done: a.resuelto, hidden: a.oculto, featured: a.destacado, reports: a.reportes || 0,
      replies: (a.respuestas || []).sort((x, y) => ts(x.creado_at) - ts(y.creado_at)).map(r => ({ id: r.id, author: r.autor_nombre || "Vecino", text: r.texto, ts: ts(r.creado_at) })) }));
    S.vr3_dir = di.map(d => ({ id: d.id, kind: d.tipo, name: d.nombre, cat: d.categoria, tip: d.descripcion, quote: d.tipo === "place" ? d.descripcion : undefined, zone: d.zona, phone: d.telefono, web: d.web, status: d.estado,
      recs: (d.recs_base || 0) + (ctx.recC[d.id] || 0), votes: (d.votos_base || 0) + (ctx.voteC[d.id] || 0), note: d.nota, origin: d.origen, ts: ts(d.creado_at) }));
    S.vr3_social = me ? { [me.email]: { recs: rc.filter(r => r.user_id === me.id).map(r => r.directorio_id), votes: Object.fromEntries(vo.filter(r => r.user_id === me.id).map(r => [r.categoria, r.directorio_id])) } } : {};
    S.vr3_twc = { circles, moai: Object.fromEntries(ml.map(m => [key(m.user_id), true])), names: Object.fromEntries(pf.map(p => [key(p.id), p.nombre])), emails: Object.fromEntries(em.map(e => [key(e.id), e.email])),
      props: pr.map(p => ({ id: p.id, kind: p.tipo, title: p.titulo, body: p.cuerpo, author: p.autor_nombre || "Vecino", email: key(p.user_id), ts: ts(p.creado_at), votes: (p.propuesta_votos || []).map(v => key(v.user_id)) })),
      plans: qd.map(p => ({ id: p.id, circle: p.circulo, sub: p.subtema, title: p.titulo, date: p.fecha, time: (p.hora || "").slice(0, 5), place: p.lugar, body: p.detalles || "", spots: p.plazas, author: p.autor_nombre || "Vecino", email: key(p.user_id), ts: ts(p.creado_at), going: (p.quedada_asistentes || []).map(v => key(v.user_id)) })) };
    S.vr3_baro = me ? { [me.email]: { consent: co[0] ? co[0].acepta : null, entries: ba.map(b => ({ id: b.id, ts: ts(b.creado_at), kind: b.tipo, answers: b.respuestas, score: b.puntuacion, change: b.cambio })) } } : {};
    S.vr_users = pf.map(p => ({ email: key(p.id), name: p.nombre, role: p.rol, code: p.codigo, invitedBy: p.invitado_por ? key(p.invitado_por) : null, since: ts(p.creado_at) }));
    S.vr_me = me; S.vr_ready = true;
    ["vr3_posts", "vr3_dir", "vr3_social", "vr3_twc", "vr3_baro", "vr_users", "vr_me", "vr_ready"].forEach(emit);
  }
  function reload() {
    if (inflight) { setTimeout(reload, 800); return Promise.resolve(); }
    if (!loading) loading = load().catch(e => console.warn(e)).finally(() => { loading = null; });
    return loading;
  }

  const byId = a => new Map((a || []).map(x => [x.id, x]));
  const diff = (a, b) => { const A = byId(a), B = byId(b); return { added: (b || []).filter(x => !A.has(x.id)), removed: (a || []).filter(x => !B.has(x.id)), changed: (b || []).filter(x => A.has(x.id) && A.get(x.id) !== x).map(x => [A.get(x.id), x]) }; };
  const sdiff = (a, b) => ({ add: (b || []).filter(x => !(a || []).includes(x)), del: (a || []).filter(x => !(b || []).includes(x)) });
  const dirRow = x => ({ tipo: x.kind, nombre: x.name, categoria: x.cat || "", descripcion: x.tip || "", zona: x.zone || "", telefono: x.phone || "", web: x.web || "", estado: x.status || "pendiente", recs_base: Math.max(0, (+x.recs || 0) - (ctx.recC[x.id] || 0)), votos_base: Math.max(0, (+x.votes || 0) - (ctx.voteC[x.id] || 0)) });

  async function push(k, prev, next) {
    const me = ctx.me; if (!sb || !me) return;
    const admin = me.role === "admin", E = me.email;
    if (k === "vr3_posts") {
      const d = diff(prev, next);
      for (const p of d.added) await chk(await sb.from("anuncios").insert({ id: p.id, tipo: p.type, titulo: p.title, cuerpo: p.body, detalle: p.detail || "", adjuntos: (p.files || []).map(({ path, name, type, size }) => ({ path, name, type, size })), caduca_at: new Date(p.expires).toISOString() }));
      for (const p of d.removed) { await chk(await sb.from("anuncios").delete().eq("id", p.id)); if ((p.files || []).length) await sb.storage.from("adjuntos").remove(p.files.map(f => f.path)); }
      for (const [o, n] of d.changed) {
        for (const r of (n.replies || []).filter(r => !(o.replies || []).includes(r))) await chk(await sb.from("respuestas").insert({ anuncio_id: n.id, texto: r.text }));
        if (!admin && (n.reports || 0) > (o.reports || 0)) { const r = await sb.from("reportes").insert({ anuncio_id: n.id }); if (r.error && r.error.code !== "23505") throw r.error; }
        const up = {};
        if (n.title !== o.title) up.titulo = n.title;
        if (n.body !== o.body) up.cuerpo = n.body;
        if ((n.detail || "") !== (o.detail || "")) up.detalle = n.detail || "";
        if (!!n.done !== !!o.done) up.resuelto = !!n.done;
        if (admin) { if (!!n.hidden !== !!o.hidden) up.oculto = !!n.hidden; if (!!n.featured !== !!o.featured) up.destacado = !!n.featured; if ((n.reports || 0) !== (o.reports || 0)) up.reportes = n.reports || 0; }
        if (Object.keys(up).length) await chk(await sb.from("anuncios").update(up).eq("id", n.id));
      }
    }
    if (k === "vr3_social") {
      const a = (prev || {})[E] || { recs: [], votes: {} }, b = (next || {})[E] || { recs: [], votes: {} };
      const r = sdiff(a.recs, b.recs);
      for (const id of r.add) await chk(await sb.from("recomendaciones").insert({ directorio_id: id }));
      for (const id of r.del) await chk(await sb.from("recomendaciones").delete().eq("user_id", me.id).eq("directorio_id", id));
      for (const c of new Set([...Object.keys(a.votes || {}), ...Object.keys(b.votes || {})])) {
        if ((a.votes || {})[c] === (b.votes || {})[c]) continue;
        if (!(b.votes || {})[c]) await chk(await sb.from("votos_locales").delete().eq("user_id", me.id).eq("categoria", c));
        else await chk(await sb.from("votos_locales").upsert({ user_id: me.id, categoria: c, directorio_id: b.votes[c] }, { onConflict: "user_id,categoria" }));
      }
    }
    if (k === "vr3_dir" && admin) {
      const d = diff(prev, next);
      for (const x of d.added) await chk(await sb.from("directorio").insert({ id: x.id, ...dirRow(x), origen: "admin" }));
      for (const x of d.removed) await chk(await sb.from("directorio").delete().eq("id", x.id));
      for (const [, x] of d.changed) await chk(await sb.from("directorio").update(dirRow(x)).eq("id", x.id));
    }
    if (k === "vr3_twc") {
      const a = prev || {}, b = next || {};
      const c = sdiff((a.circles || {})[E], (b.circles || {})[E]);
      for (const id of c.add) await chk(await sb.from("circulo_miembros").insert({ circulo: id }));
      for (const id of c.del) await chk(await sb.from("circulo_miembros").delete().eq("user_id", me.id).eq("circulo", id));
      if (!!(a.moai || {})[E] !== !!(b.moai || {})[E]) {
        if ((b.moai || {})[E]) await chk(await sb.from("moai_lista").insert({})); else await chk(await sb.from("moai_lista").delete().eq("user_id", me.id));
      }
      const pd = diff(a.props, b.props);
      for (const p of pd.added) await chk(await sb.from("propuestas").insert({ id: p.id, tipo: p.kind, titulo: p.title, cuerpo: p.body }));
      for (const p of pd.removed) await chk(await sb.from("propuestas").delete().eq("id", p.id));
      for (const [o, n] of pd.changed) { const v = sdiff((o.votes || []).filter(x => x === E), (n.votes || []).filter(x => x === E)); if (v.add.length) await chk(await sb.from("propuesta_votos").insert({ propuesta_id: n.id })); if (v.del.length) await chk(await sb.from("propuesta_votos").delete().eq("propuesta_id", n.id).eq("user_id", me.id)); }
      const qd = diff(a.plans, b.plans);
      for (const p of qd.added) { await chk(await sb.from("quedadas").insert({ id: p.id, circulo: p.circle, subtema: p.sub || "", titulo: p.title, fecha: p.date, hora: p.time, lugar: p.place, detalles: p.body || "", plazas: p.spots || null })); if ((p.going || []).includes(E)) await chk(await sb.from("quedada_asistentes").insert({ quedada_id: p.id })); }
      for (const p of qd.removed) await chk(await sb.from("quedadas").delete().eq("id", p.id));
      for (const [o, n] of qd.changed) { const v = sdiff((o.going || []).filter(x => x === E), (n.going || []).filter(x => x === E)); if (v.add.length) await chk(await sb.from("quedada_asistentes").insert({ quedada_id: n.id })); if (v.del.length) await chk(await sb.from("quedada_asistentes").delete().eq("quedada_id", n.id).eq("user_id", me.id)); }
    }
    if (k === "vr3_baro") {
      const a = (prev || {})[E] || { entries: [] }, b = (next || {})[E] || { entries: [] };
      for (const e of (b.entries || []).filter(e => !(a.entries || []).includes(e))) await chk(await sb.from("barometro").insert({ tipo: e.kind, respuestas: e.answers, puntuacion: e.score, cambio: e.change }));
      if (b.consent !== a.consent && b.consent !== null && b.consent !== undefined) await chk(await sb.from("barometro_consentimiento").upsert({ user_id: me.id, acepta: !!b.consent, actualizado_at: new Date().toISOString() }, { onConflict: "user_id" }));
    }
  }

  function errMsg(e) {
    const m = (e && (e.message || e.error_description)) || "";
    if (/completa/i.test(m)) return "Esta quedada ya está completa.";
    if (/Invalid login/i.test(m)) return "Email o contraseña incorrectos.";
    if (/not confirmed/i.test(m)) return "Confirma tu email: te enviamos un enlace al registrarte.";
    if (/already registered/i.test(m)) return "Ya existe una cuenta con ese email.";
    if (/rate limit/i.test(m)) return "Demasiados intentos. Prueba de nuevo en unos minutos.";
    if (/at least 6/i.test(m)) return "La contraseña debe tener al menos 6 caracteres.";
    if (/Database error saving new user/i.test(m)) return "No se pudo crear la cuenta. Revisa que has marcado todas las casillas.";
    return "No se ha podido guardar. Revisa tu conexión e inténtalo de nuevo.";
  }
  function update(k, upd) {
    const prev = S[k], next = typeof upd === "function" ? upd(prev) : upd;
    if (next === prev) return;
    put(k, next);
    inflight++;
    push(k, prev, next).catch(e => { console.warn(e); window.__vrFlash && window.__vrFlash(errMsg(e)); setTimeout(reload, 0); }).finally(() => { inflight = Math.max(0, inflight - 1); });
  }
  function use(k) {
    const [, f] = React.useState(0);
    React.useEffect(() => { const fn = () => f(x => x + 1); (subs[k] = subs[k] || []).push(fn); return () => { subs[k] = subs[k].filter(x => x !== fn); }; }, [k]);
    return [S[k], upd => update(k, upd)];
  }

  const redirect = () => location.origin + location.pathname;
  const api = {
    shared: k => SHARED.has(k), use, get: k => S[k], reload, errMsg,
    async signUp({ name, email, pass, invita }) {
      const r = await sb.auth.signUp({ email, password: pass, options: { emailRedirectTo: redirect(), data: { nombre: name, mayor_edad: true, reside_trabaja: true, invita: invita || null } } });
      if (r.error) throw r.error;
      if (r.data.session) { await load(); return { me: S.vr_me }; }
      return { confirm: true };
    },
    async signIn(email, pass) { const r = await sb.auth.signInWithPassword({ email, password: pass }); if (r.error) throw r.error; await load(); return S.vr_me; },
    async signInGoogle() { const r = await sb.auth.signInWithOAuth({ provider: "google", options: { redirectTo: redirect() } }); if (r.error) throw r.error; },
    async acceptTerms() { await chk(await sb.rpc("confirmar_requisitos")); await load(); },
    async resetPassword(email) { const r = await sb.auth.resetPasswordForEmail(email, { redirectTo: redirect() }); if (r.error) throw r.error; },
    async updatePassword(pass) { const r = await sb.auth.updateUser({ password: pass }); if (r.error) throw r.error; },
    async signOut() { await sb.auth.signOut(); await load(); },
    async deleteAccount(motivo) { await chk(await sb.rpc("borrar_mi_cuenta", { motivo: motivo || null })); await sb.auth.signOut().catch(() => {}); await load(); },
    async upload(postId, items) {
      const me = ctx.me; if (!sb || !me) throw new Error("Sin sesión");
      const out = [];
      for (let i = 0; i < items.length; i++) {
        const f = await vbPrepFile(items[i]);
        const safe = f.name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9._-]+/g, "-").slice(-60);
        const path = me.id + "/" + postId + "/" + (i + 1) + "-" + safe;
        const r = await sb.storage.from("adjuntos").upload(path, f.blob, { contentType: f.type, upsert: false });
        if (r.error) { if (out.length) await sb.storage.from("adjuntos").remove(out.map(x => x.path)); throw r.error; }
        out.push({ path, name: f.name, type: f.type, size: f.blob.size, url: vbFileUrl(path) });
      }
      return out;
    },
    async solicitud(row) { if (!sb) return false; const r = await sb.from("directorio").insert({ tipo: row.tipo, nombre: (row.nombre || "").slice(0, 120), categoria: row.categoria || "", telefono: (row.contacto || "").slice(0, 120), nota: (row.nota || "").slice(0, 400), origen: row.origen }); return !r.error; },
  };
  if (sb) {
    sb.auth.onAuthStateChange((ev) => {
      if (ev === "PASSWORD_RECOVERY") { window.__vrRecovery ? window.__vrRecovery() : (window.__vrRecoveryPending = true); }
      if (ev === "SIGNED_IN" || ev === "SIGNED_OUT" || ev === "USER_UPDATED") setTimeout(reload, 0);
    });
    reload();
    setInterval(() => { if (document.visibilityState === "visible") reload(); }, 60000);
    document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") reload(); });
  } else put("vr_ready", true);
  return api;
})();

Object.assign(window, { VBStore, vbId, sbClient: sb });
