// data-provider.jsx — Auth, Data, Sync für die Urlaubskasse.
// Nutzt window.UKDB und window.UKAPI. Exportiert window.UKProviders.

(function () {
  const { useState, useEffect, useMemo, useCallback, useRef, createContext, useContext } = React;

  const uuid = () => {
    if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
      const r = Math.random() * 16 | 0;
      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
  };

  // ════════════════════════════════════════════════════════════
  //  AUTH
  // ════════════════════════════════════════════════════════════
  const AuthCtx = createContext(null);

  function AuthProvider({ children }) {
    const [token,   setTokenState] = useState(() => UKAPI.getToken());
    const [email,   setEmail]      = useState(() => localStorage.getItem('uk-email') || null);
    const [error,   setError]      = useState(null);
    const [loading, setLoading]    = useState(false);

    const login = useCallback(async (em, pw) => {
      setLoading(true); setError(null);
      try {
        const r = await UKAPI.login(em.trim(), pw);
        localStorage.setItem('uk-email', r.email);
        setEmail(r.email);
        setTokenState(r.token);
        return true;
      } catch (e) {
        setError(e.message || 'Login fehlgeschlagen');
        return false;
      } finally {
        setLoading(false);
      }
    }, []);

    const logout = useCallback(() => {
      UKAPI.logout();
      localStorage.removeItem('uk-email');
      setTokenState(null);
      setEmail(null);
    }, []);

    const wipeLocal = useCallback(async () => {
      await UKDB.wipeAll();
      logout();
    }, [logout]);

    const value = useMemo(() => ({
      token, email, error, loading,
      isAuthed: !!token,
      login, logout, wipeLocal,
    }), [token, email, error, loading, login, logout, wipeLocal]);

    return <AuthCtx.Provider value={value}>{children}</AuthCtx.Provider>;
  }

  const useAuth = () => useContext(AuthCtx);

  // ════════════════════════════════════════════════════════════
  //  DATA + SYNC
  // ════════════════════════════════════════════════════════════
  const DataCtx = createContext(null);
  const SyncCtx = createContext(null);

  function DataAndSyncProvider({ children }) {
    const auth = useAuth();
    const [trips,      setTrips]      = useState([]);
    const [categories, setCategories] = useState([]);
    const [expenses,   setExpenses]   = useState([]);
    const [activeTripId, setActiveTripIdState] = useState(null);

    const [online,    setOnline]    = useState(navigator.onLine);
    const [syncing,   setSyncing]   = useState(false);
    const [lastSync,  setLastSync]  = useState(null);
    const [pending,   setPending]   = useState(0);
    const [syncError, setSyncError] = useState(null);
    const syncTimerRef = useRef(null);
    const pollTimerRef = useRef(null);
    const syncInFlightRef = useRef(false);

    const refreshPending = useCallback(async () => {
      const u = await UKDB.unsyncedRecords();
      const queued = await UKDB.pendingUploads();
      setPending(u.trips.length + u.categories.length + u.expenses.length + queued.length);
    }, []);

    const reloadFromDb = useCallback(async () => {
      const [t, c, e] = await Promise.all([
        UKDB.getAll('trips'), UKDB.getAll('categories'), UKDB.getAll('expenses'),
      ]);
      setTrips(t || []);
      setCategories(c || []);
      setExpenses(e || []);
    }, []);

    const syncNow = useCallback(async () => {
      if (!auth.isAuthed || syncInFlightRef.current) return;
      if (!navigator.onLine) return;
      syncInFlightRef.current = true;
      setSyncing(true); setSyncError(null);
      try {
        const cursor = (await UKDB.getMeta('sync_cursor')) || 0;
        const unsynced = await UKDB.unsyncedRecords();
        const strip = (rows) => rows.map(({ synced, ...rest }) => rest);
        const payload = {
          expenses:   strip(unsynced.expenses),
          categories: strip(unsynced.categories),
          trips:      strip(unsynced.trips),
        };

        const r = await UKAPI.sync(cursor, payload);

        await UKDB.markSynced('trips',      unsynced.trips.map(x => x.id));
        await UKDB.markSynced('categories', unsynced.categories.map(x => x.id));
        await UKDB.markSynced('expenses',   unsynced.expenses.map(x => x.id));

        const stamp = (rows) => (rows || []).map(r => ({ ...r, synced: 1 }));
        await UKDB.putMany('trips',      stamp(r.changes && r.changes.trips));
        await UKDB.putMany('categories', stamp(r.changes && r.changes.categories));
        await UKDB.putMany('expenses',   stamp(r.changes && r.changes.expenses));

        const queued = await UKDB.pendingUploads();
        for (const q of queued) {
          try {
            await UKAPI.upload(q.expense_id, q.blob, q.mime);
            await UKDB.removeUpload(q.expense_id);
          } catch (_) { /* nächster Versuch */ }
        }

        await reloadFromDb();
        await UKDB.setMeta('sync_cursor', r.server_time);
        const now = new Date();
        await UKDB.setMeta('last_sync_at', now.getTime());
        setLastSync(now);
        await refreshPending();
      } catch (e) {
        setSyncError(e.message || 'Sync fehlgeschlagen');
        if (e.status === 401) auth.logout();
      } finally {
        setSyncing(false);
        syncInFlightRef.current = false;
      }
    }, [auth, refreshPending, reloadFromDb]);

    const scheduleSync = useCallback(() => {
      if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
      syncTimerRef.current = setTimeout(() => { syncNow(); }, 300);
    }, [syncNow]);

    // Initial-Load + erster Sync
    useEffect(() => {
      if (!auth.isAuthed) {
        setTrips([]); setCategories([]); setExpenses([]); setActiveTripIdState(null);
        return;
      }
      let cancelled = false;
      (async () => {
        const [t, c, e, atid, ls] = await Promise.all([
          UKDB.getAll('trips'),
          UKDB.getAll('categories'),
          UKDB.getAll('expenses'),
          UKDB.getMeta('active_trip_id'),
          UKDB.getMeta('last_sync_at'),
        ]);
        if (cancelled) return;
        setTrips(t || []);
        setCategories(c || []);
        setExpenses(e || []);
        setActiveTripIdState(atid || null);
        setLastSync(ls ? new Date(ls) : null);
        await refreshPending();
        syncNow();
      })();
      return () => { cancelled = true; };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [auth.isAuthed]);

    // Online/Offline
    useEffect(() => {
      const on  = () => { setOnline(true);  if (auth.isAuthed) syncNow(); };
      const off = () => setOnline(false);
      window.addEventListener('online', on);
      window.addEventListener('offline', off);
      return () => {
        window.removeEventListener('online', on);
        window.removeEventListener('offline', off);
      };
    }, [auth.isAuthed, syncNow]);

    // 60s-Pull
    useEffect(() => {
      if (!auth.isAuthed) return;
      pollTimerRef.current = setInterval(() => {
        if (document.visibilityState === 'visible' && navigator.onLine) syncNow();
      }, 60000);
      return () => clearInterval(pollTimerRef.current);
    }, [auth.isAuthed, syncNow]);

    // ── Mutationen ─────────────────────────────────────────
    const writeRow = useCallback(async (store, row, setter) => {
      const stamped = { ...row, updated_at: Date.now(), synced: 0 };
      if (stamped.deleted == null) stamped.deleted = 0;
      await UKDB.put(store, stamped);
      setter(prev => {
        const i = prev.findIndex(r => r.id === stamped.id);
        if (i < 0) return [...prev, stamped];
        const out = prev.slice();
        out[i] = stamped;
        return out;
      });
      await refreshPending();
      scheduleSync();
      return stamped;
    }, [refreshPending, scheduleSync]);

    const addTrip = useCallback(async (data) => {
      const row = {
        id: uuid(),
        name: data.name,
        flag: data.flag || '🌍',
        start_date: data.start_date,
        end_date:   data.end_date,
        budget_cents: Math.round((+data.budget_eur || 0) * 100),
        currency: 'EUR',
        active: data.active ? 1 : 0,
      };
      const saved = await writeRow('trips', row, setTrips);
      if (row.active) {
        const others = trips.filter(t => t.id !== saved.id && t.active);
        for (const o of others) await writeRow('trips', { ...o, active: 0 }, setTrips);
        await UKDB.setMeta('active_trip_id', saved.id);
        setActiveTripIdState(saved.id);
      }
      return saved;
    }, [trips, writeRow]);

    const updateTrip = useCallback((id, patch) => {
      const cur = trips.find(t => t.id === id);
      if (!cur) return Promise.resolve(null);
      const merged = { ...cur, ...patch };
      if (patch.budget_eur != null) merged.budget_cents = Math.round((+patch.budget_eur) * 100);
      delete merged.budget_eur;
      return writeRow('trips', merged, setTrips);
    }, [trips, writeRow]);

    const removeTrip = useCallback(async (id) => {
      const cur = trips.find(t => t.id === id);
      if (!cur) return;
      await writeRow('trips', { ...cur, deleted: 1 }, setTrips);
      const exs = expenses.filter(e => e.trip_id === id && !e.deleted);
      for (const ex of exs) {
        await writeRow('expenses', { ...ex, deleted: 1 }, setExpenses);
      }
      if (activeTripId === id) {
        await UKDB.setMeta('active_trip_id', null);
        setActiveTripIdState(null);
      }
    }, [trips, expenses, activeTripId, writeRow]);

    const setActiveTrip = useCallback(async (id) => {
      const others = trips.filter(t => t.id !== id && t.active);
      for (const o of others) await writeRow('trips', { ...o, active: 0 }, setTrips);
      const target = trips.find(t => t.id === id);
      if (target && !target.active) await writeRow('trips', { ...target, active: 1 }, setTrips);
      await UKDB.setMeta('active_trip_id', id);
      setActiveTripIdState(id);
    }, [trips, writeRow]);

    const addExpense = useCallback((data) => {
      const row = {
        id: uuid(),
        trip_id: data.trip_id,
        category_id: data.category_id || null,
        date: data.date,
        time: data.time || null,
        amount_cents: Math.round((+data.amount_eur || 0) * 100),
        description: data.description || null,
        location: data.location || null,
        lat: data.lat != null ? data.lat : null,
        lng: data.lng != null ? data.lng : null,
        receipt_url: null,
        notes: data.notes || null,
      };
      return writeRow('expenses', row, setExpenses);
    }, [writeRow]);

    const updateExpense = useCallback((id, patch) => {
      const cur = expenses.find(e => e.id === id);
      if (!cur) return Promise.resolve(null);
      const merged = { ...cur, ...patch };
      if (patch.amount_eur != null) merged.amount_cents = Math.round((+patch.amount_eur) * 100);
      delete merged.amount_eur;
      return writeRow('expenses', merged, setExpenses);
    }, [expenses, writeRow]);

    const removeExpense = useCallback((id) => {
      const cur = expenses.find(e => e.id === id);
      if (!cur) return Promise.resolve();
      return writeRow('expenses', { ...cur, deleted: 1 }, setExpenses);
    }, [expenses, writeRow]);

    const addCategory = useCallback((label, emoji, color) => {
      const id = label.toLowerCase().replace(/[^a-z0-9]/g, '_').slice(0, 24) || ('c_' + Date.now().toString(36));
      const palette = ['#8b7d5a', '#5a7c8b', '#c97c5c', '#7c5c8b', '#5c8b6a', '#8b5c5c'];
      const userCount = categories.filter(c => c.is_user && !c.deleted).length;
      return writeRow('categories', {
        id, label,
        emoji: emoji || '✨',
        color: color || palette[userCount % palette.length],
        is_user: 1,
      }, setCategories);
    }, [categories, writeRow]);

    const removeCategory = useCallback((id) => {
      const cur = categories.find(c => c.id === id);
      if (!cur) return Promise.resolve();
      return writeRow('categories', { ...cur, deleted: 1 }, setCategories);
    }, [categories, writeRow]);

    const uploadReceipt = useCallback(async (expense_id, blob, mime) => {
      if (!navigator.onLine) {
        await UKDB.queueUpload(expense_id, blob, mime);
        await refreshPending();
        return null;
      }
      try {
        const r = await UKAPI.upload(expense_id, blob, mime);
        await syncNow();
        return r.url;
      } catch (e) {
        await UKDB.queueUpload(expense_id, blob, mime);
        await refreshPending();
        throw e;
      }
    }, [refreshPending, syncNow]);

    const visibleTrips      = useMemo(() => trips.filter(t => !t.deleted),      [trips]);
    const visibleCategories = useMemo(() => categories.filter(c => !c.deleted), [categories]);
    const visibleExpenses   = useMemo(() => expenses.filter(e => !e.deleted),   [expenses]);

    const activeTrip = useMemo(() => {
      if (activeTripId) {
        const t = visibleTrips.find(x => x.id === activeTripId);
        if (t) return t;
      }
      return visibleTrips.find(t => t.active) || visibleTrips[0] || null;
    }, [visibleTrips, activeTripId]);

    const dataValue = useMemo(() => ({
      trips: visibleTrips,
      categories: visibleCategories,
      expenses: visibleExpenses,
      activeTrip,
      addTrip, updateTrip, removeTrip, setActiveTrip,
      addExpense, updateExpense, removeExpense,
      addCategory, removeCategory,
      uploadReceipt,
    }), [visibleTrips, visibleCategories, visibleExpenses, activeTrip,
         addTrip, updateTrip, removeTrip, setActiveTrip,
         addExpense, updateExpense, removeExpense,
         addCategory, removeCategory, uploadReceipt]);

    const syncValue = useMemo(() => ({
      online, syncing, lastSync, pending, error: syncError,
      syncNow,
      setOnline: (v) => setOnline(v),
    }), [online, syncing, lastSync, pending, syncError, syncNow]);

    return (
      <DataCtx.Provider value={dataValue}>
        <SyncCtx.Provider value={syncValue}>
          {children}
        </SyncCtx.Provider>
      </DataCtx.Provider>
    );
  }

  const useData = () => useContext(DataCtx);
  const useSync = () => useContext(SyncCtx);

  window.UKProviders = {
    AuthProvider, useAuth,
    DataAndSyncProvider, useData, useSync,
  };
})();
