// app.jsx — state, routing, mutations, persistence
const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA, useRef: useRefA } = React;
const LS_KEY = 'planiq_state_v1';

// Betalingsmuren er slået FRA indtil Stripe + Functions er sat op og eksisterende
// brugere er grandfathered (se STRIPE_SETUP.md). Sæt til true når alt er klart,
// så ingen låses ude i mellemtiden.
const PAYWALL_ENABLED = true;

function clone(x) { return JSON.parse(JSON.stringify(x)); }
// Lokal cache bruges som hurtig opstart/offline-fallback; Firestore er kilden til sandhed for indloggede brugere.
function load() { try { return JSON.parse(localStorage.getItem(LS_KEY)); } catch (e) { return null; } }

function freshProject(d) {
  const D = window.PlaniqData;
  return {
    id: D.uid('p'), name: d.name, type: d.type, date: d.date || '', venue: '',
    cover: 1 + Math.floor(Math.random() * 6),
    coverImage: d.coverImage || null,
    guests: [], groups: D.defaultGroups(), tables: [], rules: [],
    updated: Date.now(),
  };
}

function App() {
  const saved = load();
  const seedProjects = () => clone(window.PlaniqData.projects);
  const [screen, setScreen] = useStateA(saved?.screen || 'landing');
  const [user, setUser] = useStateA(saved?.user || null);
  const [projects, setProjects] = useStateA(saved?.projects || seedProjects());
  const [activeId, setActiveId] = useStateA(saved?.activeId || null);
  const [tab, setTab] = useStateA(saved?.tab || 'guests');

  // ---- Firebase auth + Firestore-persistens ----
  const [uid, setUid] = useStateA(null);
  const [paid, setPaid] = useStateA(false); // permanent adgang (sættes af Stripe-webhook)
  // hydrated bliver sandt når brugerens data er hentet fra Firestore (eller der ikke er nogen bruger),
  // så vi ikke overskriver skyens data med tom lokal state før vi har læst den.
  const hydrated = useRefA(false);
  const saveTimer = useRefA(null);

  // Firebase-modulet indlæses asynkront (type="module"). Vent på det er klart.
  const [fbReady, setFbReady] = useStateA(!!window.PlaniqFirebase);
  useEffectA(() => {
    if (window.PlaniqFirebase) { setFbReady(true); return; }
    const onReady = () => setFbReady(true);
    window.addEventListener('planiq-firebase-ready', onReady);
    return () => window.removeEventListener('planiq-firebase-ready', onReady);
  }, []);

  // Lyt på login-tilstand. Sker ved load, login og logout.
  useEffectA(() => {
    const fb = window.PlaniqFirebase;
    if (!fb) return; // afventer fbReady
    const unsub = fb.onAuth(async (u) => {
      if (u) {
        setUid(u.uid);
        setUser(u.displayName || u.email);
        // Hent brugerens gemte plan fra Firestore.
        try {
          const remote = await fb.loadState(u.uid);
          const hasPaid = !PAYWALL_ENABLED || !!(remote && remote.paid === true);
          setPaid(hasPaid);
          if (!hasPaid) {
            // Logget ind men har ikke betalt → betalingsmur.
            setScreen('paywall');
          } else if (remote && Array.isArray(remote.projects)) {
            setProjects(remote.projects);
            setActiveId(remote.activeId ?? null);
            setTab(remote.tab || 'guests');
            setScreen(remote.activeId ? (remote.screen || 'dashboard') : 'dashboard');
          } else {
            // Betalt ny bruger: start på dashboardet med seed-state.
            setScreen('dashboard');
          }
        } catch (e) {
          console.error('Kunne ikke hente fra Firestore:', e);
          // Ved fejl: lås kun ude hvis muren er aktiv, ellers giv adgang.
          setPaid(!PAYWALL_ENABLED);
          setScreen(PAYWALL_ENABLED ? 'paywall' : 'dashboard');
        }
        hydrated.current = true;
      } else {
        // Logget ud.
        setUid(null);
        setUser(null);
        setPaid(false);
        hydrated.current = true; // ingen sky-data at vente på
      }
    });
    return unsub;
  }, [fbReady]);

  // tweaks
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "defaultTable": "round",
    "accent": "#d07c54",
    "radius": 20
  }/*EDITMODE-END*/;
  const [tw, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // persist — lokal cache med det samme, Firestore med kort debounce.
  useEffectA(() => {
    const snapshot = { screen, user, projects, activeId, tab };
    localStorage.setItem(LS_KEY, JSON.stringify(snapshot));

    // Gem kun til Firestore når en bruger er logget ind OG vi har hentet skyens data først,
    // så vi ikke overskriver den med tom/seed-state under opstart.
    const fb = window.PlaniqFirebase;
    if (!uid || !fb || !hydrated.current) return;
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(() => {
      fb.saveState(uid, { projects, activeId, tab, screen, updatedAt: Date.now() })
        .catch((e) => console.error('Kunne ikke gemme til Firestore:', e));
    }, 600);
  }, [screen, user, projects, activeId, tab, uid]);

  // apply tweaks to :root
  useEffectA(() => {
    const r = document.documentElement;
    r.style.setProperty('--accent', tw.accent);
    // derive deep/soft/tint from accent
    r.style.setProperty('--radius-base', tw.radius + 'px');
  }, [tw.accent, tw.radius]);

  const active = useMemoA(() => projects.find(p => p.id === activeId), [projects, activeId]);

  // ---- project-level mutations ----
  const patchActive = (fn) => setProjects(ps => ps.map(p => p.id === activeId ? { ...fn(p), updated: Date.now() } : p));

  const mut = useMemoA(() => ({
    updateProject: (patch) => patchActive(p => ({ ...p, ...patch })),
    // guests
    addGuest: (g) => patchActive(p => ({ ...p, guests: [...p.guests, { id: window.PlaniqData.uid('gu'), name: g.name, group: g.group, plusOne: false, plusOneName: '', child: false, diet: [], note: '', seat: null }] })),
    updateGuest: (id, patch) => patchActive(p => ({ ...p, guests: p.guests.map(x => x.id === id ? { ...x, ...patch } : x) })),
    removeGuest: (id) => patchActive(p => ({ ...p, guests: p.guests.filter(x => x.id !== id), rules: p.rules.filter(r => r.a !== id && r.b !== id) })),
    // groups
    addGroup: (name) => patchActive(p => ({ ...p, groups: [...p.groups, { id: window.PlaniqData.uid('g'), name, color: 1 + (p.groups.length % 6) }] })),
    updateGroup: (id, patch) => patchActive(p => ({ ...p, groups: p.groups.map(x => x.id === id ? { ...x, ...patch } : x) })),
    removeGroup: (id) => patchActive(p => {
      const fallback = p.groups.find(g => g.id !== id);
      // Var det den sidste gruppe? Så fjernes dens gæster også (en gæst kan ikke stå uden gruppe).
      if (!fallback) return { ...p, groups: [], guests: [], rules: [] };
      return { ...p, groups: p.groups.filter(x => x.id !== id), guests: p.guests.map(g => g.group === id ? { ...g, group: fallback.id } : g) };
    }),
    // rules
    addRule: (r) => patchActive(p => ({ ...p, rules: [...p.rules, { id: window.PlaniqData.uid('r'), ...r }] })),
    removeRule: (id) => patchActive(p => ({ ...p, rules: p.rules.filter(r => r.id !== id) })),
    // tables
    addTable: (type) => patchActive(p => {
      const defs = { round: 6, long: 8, square: 8, horseshoe: 12 };
      const n = p.tables.length;
      const labels = { round: 'Bord', long: 'Langbord', square: 'Bord', horseshoe: 'Hestesko' };
      return { ...p, tables: [...p.tables, { id: window.PlaniqData.uid('t'), label: `${labels[type]} ${n + 1}`, type, seats: defs[type], x: 380 + (n % 3) * 360, y: 280 + Math.floor(n / 3) * 360, rot: 0 }] };
    }),
    updateTable: (id, patch) => patchActive(p => ({ ...p, tables: p.tables.map(t => t.id === id ? { ...t, ...patch } : t) })),
    removeTable: (id) => patchActive(p => ({ ...p, tables: p.tables.filter(t => t.id !== id), guests: p.guests.map(g => g.seat && g.seat.tableId === id ? { ...g, seat: null } : g) })),
    duplicateTable: (id) => patchActive(p => { const t = p.tables.find(x => x.id === id); if (!t) return p; return { ...p, tables: [...p.tables, { ...t, id: window.PlaniqData.uid('t'), label: t.label + ' (kopi)', x: t.x + 80, y: t.y + 80 }] }; }),
    // seating
    assignSeat: (guestId, tableId, index, from) => patchActive(p => {
      const occupant = p.guests.find(x => x.seat && x.seat.tableId === tableId && x.seat.index === index && x.id !== guestId);
      return { ...p, guests: p.guests.map(x => {
        if (x.id === guestId) return { ...x, seat: { tableId, index } };
        if (occupant && x.id === occupant.id) return { ...x, seat: from ? { ...from } : null };
        return x;
      }) };
    }),
    unseat: (guestId) => patchActive(p => ({ ...p, guests: p.guests.map(x => x.id === guestId ? { ...x, seat: null } : x) })),
    autoPlace: () => patchActive(p => autoPlace(p)),
  }), [activeId]);

  // ---- dashboard mutations ----
  const createProject = (d) => { const np = freshProject(d); setProjects(ps => [np, ...ps]); setActiveId(np.id); setTab('guests'); setScreen('project'); };
  const duplicateProject = (id) => setProjects(ps => { const src = ps.find(p => p.id === id); if (!src) return ps; const np = clone(src); np.id = window.PlaniqData.uid('p'); np.name = src.name + ' (kopi)'; np.updated = Date.now(); return [np, ...ps]; });
  const deleteProject = (id) => { setProjects(ps => ps.filter(p => p.id !== id)); if (activeId === id) { setActiveId(null); setScreen('dashboard'); } };
  const renameProject = (id, name) => setProjects(ps => ps.map(p => p.id === id ? { ...p, name } : p));
  const setProjectCover = (id, coverImage) => setProjects(ps => ps.map(p => p.id === id ? { ...p, coverImage, updated: Date.now() } : p));

  const doLogout = () => {
    const fb = window.PlaniqFirebase;
    if (fb) fb.logout().catch(() => {});
    hydrated.current = false;
    setProjects(seedProjects()); setActiveId(null);
    setPaid(false); setScreen('landing'); setUser(null);
  };

  // ---- render ----
  // onAuthed kaldes ved succesfuldt login/opret. Selve dataindlæsningen og skift til dashboard/paywall
  // håndteres af onAuth-listeneren ovenfor; her sætter vi blot navnet med det samme for hurtig respons.
  if (screen === 'landing') return (<><Landing onAuthed={(name) => { setUser(name); }} /><Tweaks tw={tw} setTweak={setTweak} /></>);

  // Logget ind men ikke betalt → betalingsmur.
  if (uid && !paid) return (
    <Paywall user={user}
      onPaid={() => { setPaid(true); setScreen('dashboard'); }}
      onLogout={doLogout} />
  );

  if (screen === 'dashboard' || !active) return (
    <>
      <Dashboard projects={projects} user={user || 'Dig'}
        onOpen={(id) => { setActiveId(id); setTab('guests'); setScreen('project'); }}
        onCreate={createProject} onDuplicate={duplicateProject} onDelete={deleteProject} onRename={renameProject} onSetCover={setProjectCover}
        onLogout={doLogout} />
      <Tweaks tw={tw} setTweak={setTweak} />
    </>
  );

  return (
    <>
      <ProjectView project={active} tab={tab} setTab={setTab} mut={mut} tweaks={tw}
        onBack={() => setScreen('dashboard')}
        onDeleteProject={() => deleteProject(active.id)} />
      <Tweaks tw={tw} setTweak={setTweak} />
    </>
  );
}

// ---- auto-place heuristic ----
function autoPlace(p) {
  const guests = clone(p.guests);
  const byId = (id) => guests.find(g => g.id === id);
  // free seats list
  const free = [];
  p.tables.forEach(t => { for (let i = 0; i < t.seats; i++) { if (!guests.some(g => g.seat && g.seat.tableId === t.id && g.seat.index === i)) free.push({ tableId: t.id, index: i }); } });
  if (!free.length) return p;
  const tableMembers = (tid) => guests.filter(g => g.seat && g.seat.tableId === tid);
  const apartWith = (gid) => p.rules.filter(r => r.type === 'apart' && (r.a === gid || r.b === gid)).map(r => r.a === gid ? r.b : r.a);
  const togetherWith = (gid) => p.rules.filter(r => r.type === 'together' && (r.a === gid || r.b === gid)).map(r => r.a === gid ? r.b : r.a);

  // order unplaced: by group so members cluster
  const unplaced = guests.filter(g => !g.seat).sort((a, b) => (a.group || '').localeCompare(b.group || ''));
  for (const g of unplaced) {
    const forbidden = apartWith(g.id);
    const mustTbl = togetherWith(g.id).map(id => byId(id)).filter(x => x && x.seat).map(x => x.seat.tableId);
    // score each free seat's table
    let best = -1, bestScore = -1e9;
    free.forEach((seat, i) => {
      if (seat.taken) return;
      const members = tableMembers(seat.tableId);
      if (members.some(m => forbidden.includes(m.id))) return; // hard avoid apart
      let score = 0;
      if (mustTbl.length && mustTbl.includes(seat.tableId)) score += 100;
      const sameGroup = members.filter(m => m.group === g.group).length;
      score += sameGroup * 10;
      score -= members.length * 0.2; // mild spread
      if (score > bestScore) { bestScore = score; best = i; }
    });
    if (best === -1) { // fallback: any non-forbidden, else any
      best = free.findIndex(s => !s.taken && !tableMembers(s.tableId).some(m => forbidden.includes(m.id)));
      if (best === -1) best = free.findIndex(s => !s.taken);
    }
    if (best === -1) break;
    const seat = free[best]; seat.taken = true;
    g.seat = { tableId: seat.tableId, index: seat.index };
  }
  return { ...p, guests };
}

Object.assign(window, { App, autoPlace });
