// Split Crew app flow — all screens in one file
// Screens: home, create-name, create-qr, join, dashboard, add-expense, currency, settle, settle-confirm

const PEOPLE_COLORS = { Y: '#3BB4E8', E: '#E86B3B', M: '#B98DE8', S: '#7DE08B', L: '#F5C64A' };

// Max users per trip — see flow rules. Includes the creator.
const MAX_TRIP_MEMBERS = 10;
window.MAX_TRIP_MEMBERS = MAX_TRIP_MEMBERS;

function AppFlow({ deviceId, initial = 'home' }) {
  const [me] = useMe();
  // Always have a stable identity for this device.
  const stableDeviceId = React.useMemo(() => getDeviceId(), []);
  // The user's display name — falls back to 'Du' until they set one.
  const myName = (me && me.name && me.name !== 'You') ? me.name : 'Du';
  const myColor = '#3BB4E8';

  const [screen, setScreen] = React.useState(initial);
  const [lastScreen, setLastScreen] = React.useState(initial);
  // pot.id        = local key (used as expensesByTrip[id] and trip list key)
  // pot.crewId    = real Supabase UUID (set asynchronously after createCrew/lookupCrew)
  const [pot, setPot] = React.useState({ name: '', members: [myName], code: '', id: undefined, crewId: undefined });
  const [newExp, setNewExp] = React.useState({ amount: '', currency: 'EUR', category: null, desc: '', paidBy: 'Du', splitWith: ['Du'] });

  // Persist the list of trips the user has created.
  const TRIPS_KEY = 'sc-trips' + (deviceId ? '-' + deviceId : '');
  const EXPENSES_KEY = 'sc-expenses' + (deviceId ? '-' + deviceId : '');
  const [trips, setTripsState] = React.useState(() => {
    try {
      const raw = localStorage.getItem(TRIPS_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch { return []; }
  });
  // expensesByTrip is { [tripId]: Expense[] }
  const [expensesByTrip, setExpensesByTripState] = React.useState(() => {
    try {
      const raw = localStorage.getItem(EXPENSES_KEY);
      return raw ? JSON.parse(raw) : {};
    } catch { return {}; }
  });

  // Per-trip settled-transfer log: { [tripId]: number }.
  // Drives the "before any debt is settled" gate from the flow chart:
  //   - creator can delete trip / kick guests only while count === 0
  //   - guests can self-leave only when their own debt is 0 (settled or never registered)
  const SETTLED_KEY = 'tt-settled' + (deviceId ? '-' + deviceId : '');
  const [settledByTrip, setSettledByTripState] = React.useState(() => {
    try {
      const raw = localStorage.getItem(SETTLED_KEY);
      return raw ? JSON.parse(raw) : {};
    } catch { return {}; }
  });
  const setSettledByTrip = React.useCallback((next) => {
    setSettledByTripState(prev => {
      const v = typeof next === 'function' ? next(prev) : next;
      try { localStorage.setItem(SETTLED_KEY, JSON.stringify(v)); } catch {}
      return v;
    });
  }, [SETTLED_KEY]);
  const recordSettlement = React.useCallback((count = 1) => {
    if (!pot.id) return;
    setSettledByTrip(prev => ({ ...prev, [pot.id]: (prev[pot.id] || 0) + count }));
  }, [pot.id, setSettledByTrip]);
  const settlementCount = (pot.id && settledByTrip[pot.id]) || 0;
  const hasSettlements = settlementCount > 0;

  // Track which trips this device created — drives the local-only admin gate
  // before/while Supabase membership rows sync. Persists across reloads.
  const CREATED_KEY = 'tt-created' + (deviceId ? '-' + deviceId : '');
  const [createdTrips, setCreatedTripsState] = React.useState(() => {
    try {
      const raw = localStorage.getItem(CREATED_KEY);
      const arr = raw ? JSON.parse(raw) : [];
      return new Set(arr);
    } catch { return new Set(); }
  });
  const markCreated = React.useCallback((tripId) => {
    if (!tripId) return;
    setCreatedTripsState(prev => {
      if (prev.has(tripId)) return prev;
      const next = new Set(prev);
      next.add(tripId);
      try { localStorage.setItem(CREATED_KEY, JSON.stringify([...next])); } catch {}
      return next;
    });
  }, [CREATED_KEY]);

  const setTrips = React.useCallback((next) => {
    setTripsState(prev => {
      const v = typeof next === 'function' ? next(prev) : next;
      try { localStorage.setItem(TRIPS_KEY, JSON.stringify(v)); } catch {}
      return v;
    });
  }, [TRIPS_KEY]);
  const setExpensesByTrip = React.useCallback((next) => {
    setExpensesByTripState(prev => {
      const v = typeof next === 'function' ? next(prev) : next;
      try { localStorage.setItem(EXPENSES_KEY, JSON.stringify(v)); } catch {}
      return v;
    });
  }, [EXPENSES_KEY]);

  // Expenses for the active pot.
  const expenses = (pot.id && expensesByTrip[pot.id]) ? expensesByTrip[pot.id] : [];

  // Full member rows for the active crew (id, name, color, device_id, joined_at).
  // Drives admin badge + remove buttons. Index 0 is the admin (earliest joined).
  const [crewMembers, setCrewMembers] = React.useState([]);
  const setExpenses = React.useCallback((next) => {
    if (!pot.id) return;
    setExpensesByTrip(prev => {
      const cur = prev[pot.id] || [];
      const v = typeof next === 'function' ? next(cur) : next;
      return { ...prev, [pot.id]: v };
    });
  }, [pot.id, setExpensesByTrip]);

  // Generate a 6-char base32 code (no 0/O/1/I confusion)
  const generateTripCode = React.useCallback(() => {
    const ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
    let code = '';
    for (let i = 0; i < 6; i++) code += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
    return code;
  }, []);

  // Begin a brand new trip — wipes the in-memory pot so previous create-flow data doesn't leak.
  const startNewTrip = React.useCallback(() => {
    setPot({ name: '', members: [myName], code: generateTripCode(), id: undefined, crewId: undefined });
    setNewExp({ amount: '', currency: 'EUR', category: null, desc: '', paidBy: myName, splitWith: [myName] });
    setScreen('create-name');
  }, [myName]);

  // Commit the current pot as a saved trip — always assigns a fresh id when creating from blank.
  // The id is derived from the trip code so all crew members (creator + joiners)
  // converge on the same key once Supabase is wired in.
  // Commit the active pot (or an explicit one) to the recent-trips list.
  // Accepts an optional override so callers can avoid stale-closure problems
  // when committing right after setPot — pass the same pot object you just set.
  const commitTrip = React.useCallback((overridePot) => {
    const p = overridePot || pot;
    if (!p.name) return;
    const id = p.id || (p.code ? ('t_' + p.code) : ('trip-' + Date.now() + '-' + Math.floor(Math.random() * 1000)));
    const home = p.homeCurrency || 'SEK';
    const entry = {
      id,
      name: p.name,
      members: p.members ? p.members.length : 1,
      status: 'active',
      code: p.code,
      crewId: p.crewId,
      homeCurrency: home,
      fxRates: p.fxRates || null,
      fxFetchedAt: p.fxFetchedAt || null,
      createdAt: Date.now(),
    };
    setTrips(prev => {
      const without = prev.filter(t => t.id !== id);
      return [entry, ...without];
    });
    if (!p.id && !overridePot) setPot(prev => ({ ...prev, id, homeCurrency: home }));
    // Remember this device created the trip — used as a local-only admin
    // signal before Supabase membership rows sync.
    markCreated(id);

    // Fetch FX rates ONCE per trip — cached on the trip object forever
    // (per brief: "enough to check that once"). Subsequent expenses use the
    // same snapshot so debt math is stable across the trip lifetime.
    if (!entry.fxRates && window.tt && typeof window.tt.fetchFxRates === 'function') {
      window.tt.fetchFxRates(home).then(r => {
        if (!r || !r.ok) return;
        setTrips(prev => prev.map(t => (
          t.id === id ? { ...t, fxRates: r.rates, fxFetchedAt: r.fetchedAt, fxSource: r.source } : t
        )));
        // Mirror into the active pot if this is the trip currently open.
        setPot(prev => (
          prev.id === id
            ? { ...prev, fxRates: r.rates, fxFetchedAt: r.fetchedAt, fxSource: r.source }
            : prev
        ));
      }).catch(() => {});
    }
  }, [pot, setTrips, markCreated]);

  const deleteTrip = React.useCallback(() => {
    const id = pot.id;
    if (id) {
      setTrips(prev => prev.filter(t => t.id !== id));
      setExpensesByTrip(prev => { const n = { ...prev }; delete n[id]; return n; });
    }
    setPot({ name: '', members: [myName], code: '', id: undefined, crewId: undefined });
  }, [pot.id, setTrips, setExpensesByTrip, myName]);

  const openTrip = React.useCallback((trip) => {
    // Members come from DB once crewId is set; seed locally with just me until then.
    setCrewMembers([]);
    setPot({
      name: trip.name,
      members: [myName],
      code: trip.code || '',
      id: trip.id,
      crewId: trip.crewId,
      homeCurrency: trip.homeCurrency || 'SEK',
      fxRates: trip.fxRates || null,
      fxFetchedAt: trip.fxFetchedAt || null,
      fxSource: trip.fxSource || null,
    });
    setScreen('dashboard');
  }, [myName]);

  // ───────────────────────────────────────────────────────────────────
  // Supabase integration
  // ───────────────────────────────────────────────────────────────────
  // Whenever the active pot has a code but no crewId yet, ensure a remote
  // crew row exists. createCrew is idempotent (returns existing on dup code).
  React.useEffect(() => {
    const tt = window.tt;
    if (!tt || !tt.isOnline()) return;
    if (!pot.code || !pot.name) return;
    if (pot.crewId) return;
    let cancelled = false;
    (async () => {
      const res = await tt.createCrew({ name: pot.name, code: pot.code });
      if (cancelled) return;
      if (res.ok) {
        const crewId = res.crew.id;
        setPot(p => ({ ...p, crewId }));
        // Persist crewId on the trip list entry too.
        setTrips(prev => prev.map(t => (t.id === pot.id ? { ...t, crewId } : t)));
        // Register this device's identity on the crew (idempotent on device_id).
        if (myName) {
          tt.addMember({ crewId, name: myName, color: myColor, deviceId: stableDeviceId }).catch(() => {});
        }
      }
    })();
    return () => { cancelled = true; };
  }, [pot.code, pot.name, pot.id, pot.crewId, setTrips, myName, stableDeviceId]);

  // Once crewId is set: fetch authoritative member list + expense list, and subscribe to realtime.
  React.useEffect(() => {
    const tt = window.tt;
    if (!tt || !tt.isOnline()) return;
    if (!pot.crewId || !pot.id) return;
    let cancelled = false;
    const localId = pot.id;
    const crewId = pot.crewId;

    // Refresh members from DB → drives crew list, splits, and balance calc.
    // If I'm no longer in the member list, the creator has kicked me — drop
    // the trip locally so it disappears from my home page (per flow rules:
    // "once a user is deleted from a trip, the trip no longer shows on
    // that user's start page").
    const refreshMembers = async () => {
      const r = await tt.listMembers(crewId);
      if (cancelled || !r.ok) return;
      const rows = r.members || [];

      // Only enforce the "kicked → drop locally" rule if I never created this
      // trip. Creators stay even when their member row is missing
      // (e.g. brand-new local trip whose addMember hasn't completed yet),
      // otherwise their own trip would self-destruct on first sync.
      const iAmStillIn = rows.some(m => m.device_id === stableDeviceId);
      const iAmCreator = createdTrips.has(localId);
      if (!iAmStillIn && !iAmCreator && rows.length > 0) {
        setTrips(prev => prev.filter(t => t.id !== localId));
        setExpensesByTrip(prev => { const n = { ...prev }; delete n[localId]; return n; });
        setCrewMembers([]);
        setPot({ name: '', members: [myName], code: '', id: undefined, crewId: undefined });
        setScreen('home');
        try { window.alert('You were removed from this trip by the creator.'); } catch {}
        return;
      }

      setCrewMembers(rows);
      const names = rows.map(m => m.name).filter(Boolean);
      // Ensure my own name is first if I'm in the list.
      const ordered = [
        ...(names.includes(myName) ? [myName] : []),
        ...names.filter(n => n !== myName),
      ];
      setPot(p => ({
        ...p,
        members: ordered.length ? ordered : [myName],
      }));
    };

    // Refresh expenses from DB → server is authoritative for shared state.
    const refreshExpenses = async () => {
      const res = await tt.listExpenses(crewId);
      if (cancelled || !res.ok) return;
      setExpensesByTrip(prev => ({ ...prev, [localId]: res.expenses }));
    };

    refreshMembers();
    refreshExpenses();

    const unsub = tt.subscribeCrew(crewId, {
      onExpense: (payload) => {
        setExpensesByTrip(prev => {
          const cur = prev[localId] || [];
          if (payload.eventType === 'DELETE') {
            return { ...prev, [localId]: cur.filter(e => e.id !== payload.old?.id) };
          }
          if (payload.new) {
            const mapped = window.tt.fromDbExpense(payload.new);
            const without = cur.filter(e => e.id !== mapped.id);
            // INSERT/UPDATE — keep newest first
            return { ...prev, [localId]: [mapped, ...without] };
          }
          return prev;
        });
      },
      // Member added/removed/renamed → refresh the full list (cheap, infrequent).
      onMember: () => { refreshMembers(); },
    });
    return () => { cancelled = true; unsub(); };
  }, [pot.crewId, pot.id, setExpensesByTrip, myName]);

  // Add an expense — writes through to Supabase when online; falls back to local-only.
  // Translates any 'Du' placeholders to the actual user name so DB rows always carry
  // real identities (otherwise multiple devices' "Du" collide and the settle calculator breaks).
  const addExpense = React.useCallback(async (exp) => {
    const norm = {
      ...exp,
      paidBy: exp.paidBy === 'Du' ? myName : exp.paidBy,
      splitWith: (exp.splitWith || []).map(n => n === 'Du' ? myName : n),
    };
    const tt = window.tt;
    if (tt && tt.isOnline() && pot.crewId) {
      const res = await tt.addExpense(pot.crewId, norm);
      if (res.ok) {
        setExpenses(prev => {
          const without = prev.filter(e => e.id !== res.expense.id);
          return [res.expense, ...without];
        });
        return res.expense;
      }
    }
    const local = { ...norm, id: norm.id || ('local-' + Date.now()) };
    setExpenses(prev => [local, ...prev]);
    return local;
  }, [pot.crewId, setExpenses, myName]);

  // Remove a member from the active crew. If the removed member is me, also
  // delete the trip locally so it disappears from my home page.
  const removeCrewMember = React.useCallback(async (memberDeviceId) => {
    const tt = window.tt;
    if (!tt || !tt.isOnline() || !pot.crewId || !memberDeviceId) return { ok: false };
    const res = await tt.removeMember({ crewId: pot.crewId, deviceId: memberDeviceId });
    if (!res.ok) return res;
    // If I just removed myself, drop the trip locally and go home.
    if (memberDeviceId === stableDeviceId) {
      const id = pot.id;
      if (id) {
        setTrips(prev => prev.filter(t => t.id !== id));
        setExpensesByTrip(prev => { const n = { ...prev }; delete n[id]; return n; });
      }
      setCrewMembers([]);
      setPot({ name: '', members: [myName], code: '', id: undefined, crewId: undefined });
      setScreen('home');
    }
    // Otherwise the realtime onMember handler will refresh the crew list.
    return res;
  }, [pot.crewId, pot.id, stableDeviceId, myName, setTrips, setExpensesByTrip]);

  // Derive admin info from the canonical crew member list.
  // Admin = earliest-joined member (the trip creator).
  // Fallback: when crewMembers haven't synced yet (offline / pre-Supabase),
  // treat me as admin if I'm the one who created this trip on this device.
  const admin = crewMembers.length ? crewMembers[0] : null;
  const isCreatorLocal = !!(pot.id && createdTrips.has(pot.id));
  const isAdmin = admin
    ? (admin.device_id === stableDeviceId)
    : isCreatorLocal;
  const go = (s) => { setLastScreen(screen); setScreen(s); };
  const ctx = { pot, setPot, expenses, setExpenses, addExpense, newExp, setNewExp, go, lastScreen, trips, commitTrip, deleteTrip, openTrip, startNewTrip, recentTrips: trips, myName, deviceId: stableDeviceId, crewMembers, admin, isAdmin, removeCrewMember, hasSettlements, settlementCount, recordSettlement, maxMembers: MAX_TRIP_MEMBERS };

  return (
    <IOSDevice width={390} height={820} dark={true}>
      <AppCanvas noChrome={screen === 'loader'}>
        {screen === 'loader' && <LoaderScreen ctx={ctx} />}
        {screen === 'home' && <HomeScreen ctx={ctx} />}
        {screen === 'profile' && <ProfileScreen ctx={ctx} />}
        {screen === 'create-name' && <CreateNameScreen ctx={ctx} />}
        {screen === 'create-qr' && <CreateQRScreen ctx={ctx} />}
        {screen === 'invite-qr' && <InviteQRScreen ctx={ctx} />}
        {screen === 'join' && <JoinScreen ctx={ctx} />}
        {screen === 'dashboard' && <DashboardScreen ctx={ctx} />}
        {screen === 'add-expense' && <AddExpenseAppScreen ctx={ctx} />}
        {screen === 'currency' && <CurrencyScreen ctx={ctx} />}
        {screen === 'settle' && <SettleScreen ctx={ctx} />}
        {screen === 'settle-confirm' && <SettleConfirmScreen ctx={ctx} />}
      </AppCanvas>
    </IOSDevice>
  );
}

function seedExpenses() {
  // Intentionally empty — a brand new TripTab starts with no expenses.
  // Crews begin empty; data fills in as members add expenses.
  return [];
}

function AppCanvas({ children, noChrome = false }) {
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: '#000',
      color: 'var(--text)',
      fontFamily: 'var(--font-ui)',
      paddingTop: noChrome ? 0 : 56, paddingBottom: noChrome ? 0 : 34,
      overflow: 'hidden',
    }}>
      <div style={{ position: 'absolute', inset: 0, opacity: 0.5 }}>
        <HexBg />
      </div>
      <div style={{ position: 'relative', zIndex: 2, height: '100%', overflowY: 'auto', overflowX: 'hidden' }}>
        {children}
      </div>
    </div>
  );
}

function AppHeader({ back, title, right }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 20px 20px' }}>
      {back ? (
        <button onClick={back} style={{
          width: 36, height: 36, borderRadius: '50%', border: '1px solid var(--cyan-500)',
          background: 'transparent', cursor: 'pointer', color: 'var(--cyan-100)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M19 12H5M12 19l-7-7 7-7" /></svg>
        </button>
      ) : <div style={{ width: 36 }} />}
      <span style={{ fontFamily: 'var(--font-display)', letterSpacing: '0.28em', fontSize: 11, color: 'var(--cyan-500)', fontWeight: 600 }}>{title}</span>
      <div style={{ width: 36, display: 'flex', justifyContent: 'flex-end' }}>{right}</div>
    </div>
  );
}

function PrimaryBtn({ children, onClick, style = {}, pulse = false, disabled = false }) {
  return (
    <button onClick={disabled ? undefined : onClick} disabled={disabled} className={'btn btn-primary' + (pulse && !disabled ? ' pulse' : '')} style={{
      width: '100%', justifyContent: 'center', padding: '15px', fontSize: 13,
      opacity: disabled ? 0.4 : 1,
      cursor: disabled ? 'not-allowed' : 'pointer',
      ...style,
    }}>{children}</button>
  );
}
function GhostBtn({ children, onClick, style = {} }) {
  return (
    <button onClick={onClick} className="btn btn-ghost" style={{
      width: '100%', justifyContent: 'center', padding: '15px', fontSize: 13, ...style,
    }}>{children}</button>
  );
}

Object.assign(window, { AppFlow, AppHeader, PrimaryBtn, GhostBtn, AppCanvas, PEOPLE_COLORS });
