// app.jsx — router, auth state, data wiring
const { useState: useA, useEffect: useAE, useMemo: useAM } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#DC4B2E",
  "displayFont": "Bricolage Grotesque",
  "radius": "soft",
  "dark": false
}/*EDITMODE-END*/;

const RADII = { sharp: { card: "6px", btn: "8px" }, soft: { card: "16px", btn: "999px" }, round: { card: "24px", btn: "999px" } };

// Seeded applicants for demo tasks (mock mode only)
const SEED_APPLICANTS = {
  [TASKS[0].id]: [
    { id: "a1", handle: "arjun", msg: "I'm 5 minutes from Sarath City Mall and free this afternoon. Happy to grab all the photos you need!", price: 550, when: "today", status: "pending" },
    { id: "a2", handle: "dev",   msg: "I visit that Apple Store regularly and can go right now. Will send photos within the hour.", price: 600, when: "asap",  status: "pending" },
    { id: "a3", handle: "priya", msg: "This is on my way home from work. Free from 5pm.", price: 480, when: "today", status: "pending" },
  ],
  [TASKS[3].id]: [
    { id: "a4", handle: "tomi", msg: "I attend T-Hub events regularly and take structured notes.", price: 800, when: "this-week", status: "pending" },
    { id: "a5", handle: "sana", msg: "I've summarised meetup announcements for 3 other clients.", price: 750, when: "flexible", status: "pending" },
  ],
};
window._seedApplicants = SEED_APPLICANTS;

const SEED_META   = {
  [TASKS[6].id]: { status: "assigned", assignedTo: "__me" },
  [TASKS[2].id]: { status: "completed", assignedTo: "__me", proof: "Delivered the gift to Lower Parel by 5:45pm — handover photo sent.", review: { stars: 5, text: "On time, thank you!" } },
  [TASKS[0].id]: { status: "completed", assignedTo: "arjun", proof: "Photographed the display + price cards. 256GB ₹89,900, 512GB ₹1,09,900 — in stock.", review: { stars: 5, text: "Fast and thorough!" } },
  [TASKS[3].id]: { status: "completed", assignedTo: "tomi", proof: "Covered the T-Hub meetup — slide photos + 1-page summary sent.", review: { stars: 5, text: "Great notes." } },
};
const SEED_APPS   = [
  { taskId: TASKS[6].id, msg: "Great natural light at home and a decent phone camera!", price: 140, when: "this-week" },
  { taskId: TASKS[2].id, msg: "I'm near Bandra and can deliver this afternoon.", price: 450, when: "today" },
];

// demo-mode pending ID submissions for the admin queue
const SEED_PENDING = [
  { id: "priya", name: "Priya Nair", handle: "@priyan", city: "Mumbai", phone: "+91 98••• ••231", idPhotoUrl: null },
  { id: "sana",  name: "Sana Q.",    handle: "@sanaq",  city: "Remote", phone: "+91 90••• ••884", idPhotoUrl: null },
];

// demo-mode seeded notifications (so the bell is populated)
const _mins = (n) => Date.now() - n * 60000;
const SEED_NOTIFS = [
  { id: "sn1", type: "accepted",    title: "You're hired! 🎉",      body: 'Meera accepted you for "Check iPad Pro availability".', taskId: TASKS[0].id, time: _mins(28),    read: false },
  { id: "sn2", type: "review",      title: "New 5★ review",          body: "Priya rated you 5 stars for the gift delivery.",         taskId: TASKS[2].id, time: _mins(180),   read: false },
  { id: "sn3", type: "application", title: "New application",         body: 'Dev applied to your task "Attend a startup meetup".',    taskId: TASKS[3].id, time: _mins(320),   read: false },
  { id: "sn4", type: "released",    title: "Payment released",        body: "You received ₹450 for the birthday-gift delivery.",      taskId: TASKS[2].id, time: _mins(1440),  read: true  },
  { id: "sn5", type: "verified",    title: "You're verified ✓",       body: "Your identity check was approved — paid tasks unlocked.", time: _mins(2880),  read: true  },
];

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // ── auth ──────────────────────────────────────────────────
  const demoMode = !isSupabaseConfigured();
  const [authUser,    setAuthUser]    = useA(null);     // { id, ... }
  const [authProfile, setAuthProfile] = useA(null);     // profile row
  const [authLoading, setAuthLoading] = useA(!demoMode);
  const [showIDVerify, setShowIDVerify] = useA(false);
  const [showOnboarding, setShowOnboarding] = useA(false);
  const [recoveryMode, setRecoveryMode] = useA(false);  // set new password after reset link

  // load the user's profile — creating it if the row doesn't exist yet
  const ensureProfile = async (user) => {
    let { profile } = await Profiles.get(user.id);
    const isNew = !profile;
    if (!profile) {
      const base = (user.email ? user.email.split("@")[0] : "user").replace(/[^a-zA-Z0-9 ]/g, "") || "user";
      await Profiles.upsert(user.id, {
        name: base.charAt(0).toUpperCase() + base.slice(1),
        handle: "@" + base.toLowerCase().slice(0, 16) + Math.floor(Math.random() * 90 + 10),
      });
      profile = (await Profiles.get(user.id)).profile;
    }
    setAuthUser(user);
    setAuthProfile(profile);
    setAuthLoading(false);
    if (isNew || !profile.phone) setShowOnboarding(true);
  };

  useAE(() => {
    if (demoMode) return;
    Auth.getSession().then(({ session }) => {
      if (session?.user) {
        ensureProfile(session.user);
      } else {
        setAuthLoading(false);
      }
    });
    const unsub = Auth.onAuthChange((event, session) => {
      if (event === "PASSWORD_RECOVERY") { setRecoveryMode(true); setAuthLoading(false); return; }
      if (session?.user) {
        ensureProfile(session.user);
      } else {
        setAuthUser(null);
        setAuthProfile(null);
      }
    });
    return unsub;
  }, []);

  const logout = async () => {
    await Auth.signOut();
    setAuthUser(null);
    setAuthProfile(null);
    setRecoveryMode(false);
    go("home");
  };

  const [demoProfile, setDemoProfile] = useA({});   // in-session profile edits for demo mode
  const currentUserId = authUser?.id || "__me";
  const currentProfile = demoMode ? { ...ME, ...demoProfile } : (authProfile || ME);
  // demo = open (prototype); real = only users flagged is_admin in the DB
  const isAdmin = demoMode ? true : !!authProfile?.isAdmin;

  // ── verification ──────────────────────────────────────────
  const [demoVerify, setDemoVerify] = useA("unverified");      // demo worker status
  const [rejectReason, setRejectReason] = useA("");            // why my submission was rejected
  const verification = demoMode ? demoVerify : (authProfile?.verificationStatus || "unverified");
  const myRejectReason = demoMode ? rejectReason : (authProfile?.rejectReason || "");

  const [pendingSubs,  setPendingSubs]  = useA(demoMode ? SEED_PENDING : []);
  const [adminLoading, setAdminLoading] = useA(false);
  const [adminUsers,   setAdminUsers]   = useA([]);   // real-mode user list
  const [paidOut,      setPaidOut]      = useA(new Set());
  const [removedIds,   setRemovedIds]   = useA(new Set());

  // ── nav ───────────────────────────────────────────────────
  const parseHash = () => {
    const hash = window.location.hash.slice(1) || "home";
    const [path, query] = hash.split("?");
    const p = {};
    if (query) {
      query.split("&").forEach(pair => {
        const [k, v] = pair.split("=");
        if (k) p[k] = decodeURIComponent(v);
      });
    }
    return { view: path, params: p };
  };

  const toHash = (v, p = {}) => {
    let hash = v;
    const keys = Object.keys(p);
    if (keys.length > 0) {
      const q = keys.map(k => `${k}=${encodeURIComponent(p[k])}`).join("&");
      hash += `?${q}`;
    }
    return hash;
  };

  const [route, setRoute] = useA(() => parseHash());
  const view = route.view;
  const params = route.params;

  const go = (v, p = {}) => {
    window.location.hash = toHash(v, p);
  };
  const openTask = (id) => go("task", { taskId: id });
  const openProfile = (id) => { if (id && id !== "__me") go("user", { userId: id }); };

  useAE(() => {
    const handleHash = () => {
      setRoute(parseHash());
    };
    window.addEventListener("hashchange", handleHash);
    return () => window.removeEventListener("hashchange", handleHash);
  }, []);

  useAE(() => { window.scrollTo({ top: 0, behavior: "smooth" }); }, [view, params.taskId]);

  // ── tasks (real DB or mock) ───────────────────────────────
  const [dbTasks,    setDbTasks]    = useA([]);
  const [extraTasks, setExtra]      = useA([]);   // locally posted (mock mode)
  const [dbLoading,  setDbLoading]  = useA(false);
  const [myPosted,   setMyPosted]   = useA([]);   // user's own posted tasks (live mode)

  useAE(() => {
    if (demoMode) return;
    setDbLoading(true);
    Tasks.list().then(({ tasks }) => { setDbTasks(tasks || []); setDbLoading(false); });
  }, [demoMode]);

  // load user's own posted tasks in live mode
  useAE(() => {
    if (demoMode || !authUser) return;
    Tasks.myPosted(authUser.id).then(({ tasks }) => setMyPosted(tasks || []));
  }, [demoMode, authUser]);

  const refreshTasks = () => {
    if (demoMode) return;
    Tasks.list().then(({ tasks }) => setDbTasks(tasks || []));
    if (authUser) {
      Tasks.myPosted(authUser.id).then(({ tasks }) => setMyPosted(tasks || []));
    }
  };

  const [payOverrides, setPayOverrides] = useA({});  // { taskId: { amount, originalAmount } } — must be declared before allTasks (which reads it)

  const allTasks = useAM(() => {
    const base = demoMode ? [...extraTasks, ...TASKS] : [...extraTasks, ...dbTasks];
    // Merge in user's own posted tasks (they may not be in dbTasks if status isn't 'open')
    const seen = new Set(base.map(t => t.id));
    const merged = [...base];
    if (!demoMode) {
      for (const t of myPosted) {
        if (!seen.has(t.id)) { merged.push(t); seen.add(t.id); }
      }
    }
    return merged.filter(t => !removedIds.has(t.id)).map(t => {
      const ov = payOverrides[t.id];
      if (ov) return { ...t, pay: { ...t.pay, amount: ov.amount, originalAmount: ov.originalAmount } };
      return t;
    });
  }, [demoMode, extraTasks, dbTasks, myPosted, removedIds, payOverrides]);

  const [taskMeta,     setTaskMeta]  = useA(SEED_META);
  const [taskApps,     setTaskApps]  = useA(SEED_APPLICANTS);

  // ── my applications (mock mode) ───────────────────────────
  const [applications, setApps] = useA(SEED_APPS);
  const appliedIds = useAM(() => new Set(applications.map(a => a.taskId)), [applications]);

  // ── saved / messages / toast ──────────────────────────────
  const [saved,      setSaved]      = useA(new Set());
  const [messages,   setMessages]   = useA({});
  const [msgModal,   setMsgModal]   = useA(null);
  const [msgProfile, setMsgProfile] = useA(null);   // resolved profile of chat partner
  const [unreadCount, setUnreadCount] = useA(0);
  const [notifications, setNotifications] = useA(demoMode ? SEED_NOTIFS : []);
  const [toast,      setToast]      = useA(null);
  const [confetti,   setConfetti]   = useA(false);
  const fireConfetti = React.useCallback(() => { setConfetti(true); setTimeout(() => setConfetti(false), 4000); }, []);

  // ── wallet ────────────────────────────────────────────────
  const [walletBalance, setWalletBalance] = useA(undefined);
  const [walletCur, setWalletCur] = useA("₹");

  const refreshWallet = async () => {
    if (demoMode) {
      const { wallet } = await Wallet.getBalance("__me");
      if (wallet) setWalletBalance(Number(wallet.balance) || 0);
      return;
    }
    if (!authUser) return;
    const { wallet } = await Wallet.getBalance(authUser.id);
    if (wallet) {
      setWalletBalance(Number(wallet.balance) || 0);
      setWalletCur(wallet.currency === "INR" ? "₹" : wallet.currency || "₹");
    }
  };

  const notifUnread = notifications.filter(n => !n.read).length;
  const notify = (n) => setNotifications(prev => [{ id: "n" + Date.now() + Math.random().toString(36).slice(2, 5), time: Date.now(), read: false, ...n }, ...prev]);
  const markNotifsRead = () => setNotifications(prev => prev.some(n => !n.read) ? prev.map(n => ({ ...n, read: true })) : prev);
  const openNotif = (n) => { if (n.taskId) openTask(n.taskId); };

  // load saved + unread count on login
  useAE(() => {
    // Load wallet balance in demo mode from localStorage
    if (demoMode) {
      Wallet.getBalance("__me").then(({ wallet }) => {
        if (wallet) setWalletBalance(Number(wallet.balance) || 0);
      });
      return;
    }
    if (!authUser) return;
    Saved.list(authUser.id).then(({ ids }) => setSaved(ids));
    Messages.unreadCount(authUser.id).then(({ count }) => setUnreadCount(count || 0));
    // load wallet balance
    Wallet.getBalance(authUser.id).then(({ wallet }) => {
      if (wallet) {
        setWalletBalance(Number(wallet.balance) || 0);
        setWalletCur(wallet.currency === "INR" ? "₹" : wallet.currency || "₹");
      }
    });
    // global subscription for incoming messages (unread badge)
    const unsub = Messages.subscribeToIncoming(authUser.id, () => {
      setUnreadCount(n => n + 1);
    });
    return unsub;
  }, [authUser]);

  // Load applications and assignments for the current task in live mode
  useAE(() => {
    if (demoMode || !params.taskId || view !== "task") return;

    let active = true;

    // Load applications
    Applications.forTask(params.taskId).then(({ apps, error }) => {
      if (active && !error) {
        setTaskApps(prev => ({ ...prev, [params.taskId]: apps }));
      }
    });

    // Load assignment
    Assignments.get(params.taskId).then(({ assignment }) => {
      if (active) {
        const t = allTasks.find(x => x.id === params.taskId);
        const taskStatus = t?.status || "open";

        if (assignment) {
          const workerProfile = profileFromRow(assignment.profiles);
          const workerId = assignment.worker_id;
          const posterId = t?.poster;

          Reviews.forTask(params.taskId).then(({ reviews }) => {
            if (!active) return;
            let review = null;
            let workerReview = null;
            for (const r of (reviews || [])) {
              if (r.reviewer_id === posterId) {
                review = { stars: r.stars, text: r.body };
              }
              if (r.reviewer_id === workerId) {
                workerReview = { stars: r.stars, text: r.body };
              }
            }
            setTaskMeta(prev => ({
              ...prev,
              [params.taskId]: {
                status: taskStatus,
                assignedTo: workerId,
                workerProfile,
                proof: assignment.proof_text,
                proofPhotos: assignment.proof_photos || [],
                review,
                workerReview,
              }
            }));
          });
        } else {
          setTaskMeta(prev => ({
            ...prev,
            [params.taskId]: {
              status: taskStatus,
              assignedTo: null,
            }
          }));
        }
      }
    });

    return () => { active = false; };
  }, [params.taskId, view, demoMode, allTasks]);

  const getMeta = (taskId) => taskMeta[taskId] || { status: "open" };
  const taskStatus = (t) => (demoMode ? getMeta(t.id).status : (t.status || "open"));

  // ── admin data (computed) ─────────────────────────────────
  const adminData = useAM(() => {
    // users
    let users;
    if (demoMode) {
      users = Object.entries(PEOPLE).map(([key, p]) => ({
        id: key, name: p.name, handle: p.handle, city: p.city,
        verified: key === "__me" ? verification === "verified" : p.verified,
        rating: p.rating, jobs: p.jobs,
        verificationStatus: key === "__me" ? verification
          : p.verified ? "verified"
          : pendingSubs.some(s => s.id === key) ? "pending" : "unverified",
      }));
    } else {
      users = adminUsers;
    }

    // tasks (with poster name + status)
    const tasks = allTasks.map(t => ({
      id: t.id, title: t.title, city: t.city, pay: t.pay,
      posterName: t.poster === "__me" ? (currentProfile.name || "You") : (PEOPLE[t.poster]?.name || t.posterProfile?.name || "Someone"),
      status: taskStatus(t),
    }));

    // payouts — completed, paid tasks owe the worker
    const payouts = allTasks
      .filter(t => taskStatus(t) === "completed" && t.pay.kind === "fixed")
      .map(t => {
        const m = getMeta(t.id);
        const worker = m.assignedTo ? PEOPLE[m.assignedTo] : null;
        return {
          taskId: t.id, title: t.title,
          workerName: worker ? worker.name : "Worker",
          amount: t.pay.amount, cur: t.pay.cur,
          paid: paidOut.has(t.id),
        };
      });

    const stats = {
      users: users.length,
      open: tasks.filter(t => t.status === "open").length,
      inProgress: tasks.filter(t => t.status === "assigned" || t.status === "proof_submitted").length,
      completed: tasks.filter(t => t.status === "completed").length,
    };

    return { users, tasks, payouts, stats };
  }, [demoMode, allTasks, taskMeta, pendingSubs, paidOut, adminUsers, verification, currentProfile]);

  // pending verifications mapped into the admin panel's shape (real data)
  const adminVerifications = useAM(() => pendingSubs.map(s => ({
    id: s.id, name: s.name, city: s.city || "", submitted: "recently", status: "pending",
    docType: "Aadhaar Card", docId: s.aadhaarLast4 ? `•••• ${s.aadhaarLast4}` : "Submitted",
    hasSelfie: !!s.selfieUrl, hasVideo: !!s.videoUrl,
    email: s.email || "", phone: s.phone || "",
    aadhaarName: s.aadhaarName || "", upiId: s.upiId || "",
    idPhotoUrl: s.idPhotoUrl || null, selfieUrl: s.selfieUrl || null, videoUrl: s.videoUrl || null,
  })), [pendingSubs]);

  const showToast = (msg) => {
    setToast(msg);
    clearTimeout(window.__tt);
    window.__tt = setTimeout(() => setToast(null), 2800);
  };

  // ── handlers ─────────────────────────────────────────────

  const apply = async (taskId, data) => {
    if (currentProfile?.banned) {
      alert("You are permanently banned from applying to tasks.");
      return;
    }
    if (!demoMode) {
      await Applications.apply(taskId, currentUserId, data);
    }
    setApps(a => [{ taskId, ...data }, ...a]);
    showToast("Application sent — the poster will be in touch.");
  };

  const publish = async (fields) => {
    if (currentProfile?.banned) {
      alert("You are permanently banned from posting tasks.");
      return;
    }
    let id;
    if (demoMode) {
      id = "u" + Date.now();
      const task = {
        id, cat: fields.cat, poster: "__me", title: fields.title.trim(),
        city: fields.city, remote: fields.remote, posted: "just now", applicants: 0, urgent: fields.urgent,
        blurb: fields.blurb.trim(), details: fields.details.map(x => x.trim()).filter(Boolean),
        proof: fields.proof.trim() || "As agreed.", deadline: fields.deadline || null,
        pay: fields.kind === "favor"
          ? { kind: "favor", note: fields.favorNote.trim() || "A favor 🙌" }
          : { kind: "fixed", amount: Number(fields.amount), cur: fields.cur },
      };
      setExtra(x => [task, ...x]);
    } else {
      const { id: newId } = await Tasks.create(currentUserId, fields);
      id = newId;
      // refresh both the browse list and the user's own posted tasks
      const [listResult, postedResult] = await Promise.all([
        Tasks.list(),
        Tasks.myPosted(currentUserId),
      ]);
      setDbTasks(listResult.tasks || []);
      setMyPosted(postedResult.tasks || []);
    }
    showToast("Your task is live!");
    if (demoMode) setTimeout(() => notify({ type: "application", title: "New application", body: `Someone applied to "${(fields.title || "your task").trim()}".`, taskId: id }), 4500);
    go("task", { taskId: id });
  };

  const acceptApp = async (taskId, app) => {
    if (!demoMode) {
      await Applications.accept(app.id, taskId, app.handle);
      refreshTasks();
    }
    setTaskApps(prev => ({
      ...prev,
      [taskId]: (prev[taskId] || []).map(a =>
        a.id === app.id ? { ...a, status: "accepted" }
        : a.status === "pending" ? { ...a, status: "rejected" } : a
      ),
    }));
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), status: "assigned", assignedTo: app.handle } }));
    showToast(`${PEOPLE[app.handle]?.name || "They"} is on the task!`);
  };

  const rejectApp = async (taskId, appId) => {
    if (!demoMode) await Applications.reject(appId);
    setTaskApps(prev => ({
      ...prev,
      [taskId]: (prev[taskId] || []).map(a => a.id === appId ? { ...a, status: "rejected" } : a),
    }));
  };

  const releasePayment = async (taskId, proof) => {
    const task = allTasks.find(t => t.id === taskId);
    const isPaidTask = task?.pay?.kind === "fixed";

    if (!demoMode) {
      await Assignments.releasePayment(taskId, proof);
      refreshTasks();
      // Trigger real payout via Razorpay
      if (isPaidTask) {
        const { data: payResult, error: payError } = await Payments.releasePayout(taskId);
        if (payError) {
          console.error("Payout error:", payError);
          // Still mark as complete — admin can handle manually
        }
        if (payResult) {
          setTaskMeta(prev => ({ ...prev, [taskId]: {
            ...getMeta(taskId), status: "completed", proof,
            paymentStatus: "released",
            paymentInfo: { payout_amount: payResult.payout_amount, commission_amount: payResult.commission_amount },
          } }));
          const cur = task?.pay?.cur || "₹";
          const workerAmt = payResult.payout_amount || 0;
          showToast(`Payment released! ${cur}${workerAmt.toLocaleString()} sent to worker.`);
          refreshWallet();
          return;
        }
      }
    }

    // Demo mode or favor task — just update meta
    const commissionRate = window.PLATFORM_COMMISSION || 10;
    const payoutAmount = isPaidTask ? Math.round(task.pay.amount * (100 - commissionRate)) / 100 : 0;

    // Credit the worker's wallet in demo mode
    if (demoMode && isPaidTask && payoutAmount > 0) {
      const updated = Wallet.creditDemo(payoutAmount, task.title, taskId);
      setWalletBalance(updated.balance);
    }

    setTaskMeta(prev => ({ ...prev, [taskId]: {
      ...getMeta(taskId), status: "completed", proof,
      paymentStatus: isPaidTask ? "released" : undefined,
      paymentInfo: isPaidTask ? { payout_amount: payoutAmount, commission_amount: task.pay.amount - payoutAmount } : undefined,
    } }));
    if (isPaidTask) fireConfetti();
    showToast(isPaidTask ? `Payment released! Task complete.` : "Task complete!");
  };

  // poster pulls their money back — cancel an open task, or reject/refund an assigned one
  const cancelTask = async (taskId, reason) => {
    const task = allTasks.find(t => t.id === taskId);
    const isPaidTask = task?.pay?.kind === "fixed";

    if (!demoMode) {
      // Trigger refund via Razorpay for paid tasks
      if (isPaidTask) {
        const { error: refundError } = await Payments.refund(taskId, reason);
        if (refundError) {
          console.error("Refund error:", refundError);
          // Continue with cancellation anyway — admin can handle
        }
      }
      await Tasks.updateStatus(taskId, "cancelled");
      refreshTasks();
      refreshWallet();
    } else if (isPaidTask) {
      // credit the demo wallet in demo mode
      const updated = Wallet.creditDemo(task.pay.amount, `Refund for "${task.title}"`, taskId);
      setWalletBalance(updated.balance);
    }

    setTaskMeta(prev => ({ ...prev, [taskId]: {
      ...getMeta(taskId), status: "cancelled", cancelReason: reason || "",
      paymentStatus: isPaidTask ? "refunded" : undefined,
    } }));
    showToast(isPaidTask ? "Task cancelled — your payment has been refunded." : "Task cancelled.");
    go("dashboard");
  };

  const leaveReview = async (taskId, review) => {
    if (!demoMode) {
      const meta = getMeta(taskId);
      await Reviews.submit(taskId, currentUserId, meta.assignedTo, review.stars, review.text);
    }
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), review } }));
    showToast("Review submitted. Thanks!");
  };

  // the WORKER reviews the POSTER (two-sided reviews)
  const reviewPoster = async (taskId, review) => {
    const task = allTasks.find(t => t.id === taskId);
    if (!demoMode && task) await Reviews.submit(taskId, currentUserId, task.poster, review.stars, review.text);
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), workerReview: review } }));
    showToast("Thanks for rating the poster!");
  };

  // worker withdraws — pulls their application, or steps off an assigned task (back to open)
  const withdrawTask = async (taskId) => {
    setApps(a => a.filter(x => x.taskId !== taskId));
    const meta = getMeta(taskId);
    const wasAssignedToMe = meta.assignedTo === "__me" || meta.assignedTo === currentUserId;
    if (wasAssignedToMe) {
      if (!demoMode) {
        await Tasks.updateStatus(taskId, "open");
        refreshTasks();
      }
      setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), status: "open", assignedTo: null } }));
    }
    showToast(wasAssignedToMe ? "You've stepped off this task — it's open again." : "Application withdrawn.");
    go("dashboard");
  };

  // poster edits their task (title/desc/budget/deadline/urgent)
  const editTask = async (taskId, fields) => {
    if (!demoMode) {
      await Tasks.update(taskId, fields);
      Tasks.list().then(({ tasks }) => setDbTasks(tasks || []));
    }
    setExtra(x => x.map(t => t.id === taskId ? {
      ...t,
      title: fields.title ?? t.title,
      blurb: fields.blurb ?? t.blurb,
      deadline: fields.deadline !== undefined ? fields.deadline : t.deadline,
      urgent: fields.urgent ?? t.urgent,
      pay: fields.amount !== undefined ? { ...t.pay, amount: fields.amount } : t.pay,
    } : t));
    showToast("Task updated.");
  };

  const boostBudget = async (taskId, newAmount) => {
    const task = allTasks.find(t => t.id === taskId);
    if (!task || task.pay.kind !== "fixed") return;
    const originalAmount = task.pay.originalAmount || task.pay.amount;
    const diff = newAmount - task.pay.amount;

    if (diff <= 0) {
      showToast("New budget must be higher than current budget.");
      return;
    }

    if (demoMode) {
      setPayOverrides(prev => ({ ...prev, [taskId]: { amount: newAmount, originalAmount } }));
      showToast(`Budget boosted to ${task.pay.cur}${newAmount.toLocaleString()}! Extra funds added to escrow.`);
      return;
    }

    try {
      const cur = task.pay.cur === "₹" ? "INR" : task.pay.cur === "$" ? "USD" : task.pay.cur === "€" ? "EUR" : task.pay.cur === "£" ? "GBP" : "INR";
      const { data: orderData, error: orderError } = await Payments.createOrder(taskId, diff, cur);

      if (orderError) {
        showToast("Error starting payment order: " + orderError);
        return;
      }

      if (orderData.mock) {
        await Tasks.update(taskId, { amount: newAmount });
        await Payments.updateAmount(taskId, newAmount);
        refreshTasks();
        showToast(`Budget boosted to ${task.pay.cur}${newAmount.toLocaleString()}! Extra funds added to escrow.`);
        return;
      }

      Payments.openCheckout(
        {
          orderId: orderData.order_id,
          amount: diff,
          currency: orderData.currency || "INR",
          taskTitle: `Boost budget for "${task.title}"`,
          userEmail: authUser?.email || "",
          userName: currentProfile?.name || "",
        },
        async (paymentResponse) => {
          const { error: verifyError } = await Payments.verifyPayment(
            taskId,
            paymentResponse.razorpay_payment_id,
            paymentResponse.razorpay_order_id,
            paymentResponse.razorpay_signature
          );

          if (verifyError) {
            showToast("Payment verification failed: " + verifyError);
            return;
          }

          const { error: taskUpdErr } = await Tasks.update(taskId, { amount: newAmount });
          const { error: payUpdErr } = await Payments.updateAmount(taskId, newAmount);

          if (taskUpdErr || payUpdErr) {
            console.error("DB Update Error:", { taskUpdErr, payUpdErr });
            showToast("Payment verified, but database update failed. Please contact support.");
            return;
          }

          refreshTasks();
          showToast(`Budget boosted to ${task.pay.cur}${newAmount.toLocaleString()}! Extra funds added to escrow.`);
        },
        (failureMsg) => {
          showToast(failureMsg || "Payment cancelled or failed.");
        }
      );
    } catch (err) {
      console.error("Budget boost error:", err);
      showToast("Failed to initiate boost payment.");
    }
  };

  const submitProof = async (taskId, data) => {
    // data = { text, photos: [{ url, file }] }
    let photoUrls = (data.photos || []).map(p => p.url);
    if (!demoMode) {
      const files = (data.photos || []).map(p => p.file).filter(Boolean);
      const { urls } = await Assignments.uploadProofPhotos(taskId, files);
      photoUrls = urls;
      await Assignments.submitProof(taskId, data.text, photoUrls);
      refreshTasks();
    }
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), status: "proof_submitted", proof: data.text, proofPhotos: photoUrls } }));
    showToast("Proof submitted — the poster will release payment soon.");
  };

  const toggleSave = async (taskId) => {
    const isSaved = saved.has(taskId);
    setSaved(prev => {
      const next = new Set(prev);
      isSaved ? next.delete(taskId) : next.add(taskId);
      return next;
    });
    if (!demoMode && authUser) await Saved.toggle(authUser.id, taskId, isSaved);
  };

  const openMessage = (handle) => {
    setMsgModal(handle);
    if (demoMode) {
      setMsgProfile(PEOPLE[handle] || null);
    } else {
      Profiles.get(handle).then(({ profile }) => setMsgProfile(profile));
    }
  };

  const setMsgStatus = (handle, id, status) => setMessages(prev => ({
    ...prev,
    [handle]: (prev[handle] || []).map(m => m.id === id ? { ...m, status } : m),
  }));

  const sendMessage = async (handle, data) => {
    // data: { text, photo: { url, file } | null }  (string also accepted)
    const text = typeof data === "string" ? data : (data.text || "");
    const photoObj = (typeof data === "object" && data) ? data.photo : null;
    const id = "m" + Date.now() + Math.random().toString(36).slice(2, 6);
    let photoUrl = photoObj?.url || null;

    setMessages(prev => ({
      ...prev,
      [handle]: [...(prev[handle] || []), { id, from: "me", text, photo: photoUrl, status: "sent", time: Date.now() }],
    }));

    if (demoMode) {
      // simulate delivery → seen so the ticks are visible
      setTimeout(() => setMsgStatus(handle, id, "delivered"), 700);
      setTimeout(() => setMsgStatus(handle, id, "seen"), 1700);
      // simulate a reply so the conversation feels alive
      const who = PEOPLE[handle]?.name?.split(" ")[0] || "They";
      const replies = ["Sounds good 👍", "Sure, that works for me.", "Great — see you then!", "Thanks for the heads up.", "On it!"];
      setTimeout(() => setMessages(prev => ({
        ...prev,
        [handle]: [...(prev[handle] || []), { id: id + "r", from: "them", text: replies[Math.floor(Math.random() * replies.length)], status: "seen", time: Date.now() }],
      })), 2600);
      return;
    }

    if (authUser) {
      if (photoObj?.file) {
        const { urls } = await Assignments.uploadProofPhotos("chat-" + authUser.id, [photoObj.file]);
        photoUrl = urls?.[0] || photoUrl;
        setMessages(prev => ({ ...prev, [handle]: (prev[handle] || []).map(m => m.id === id ? { ...m, photo: photoUrl } : m) }));
      }
      await Messages.send(authUser.id, handle, text, null, photoUrl);
      setMsgStatus(handle, id, "delivered");
    }
  };

  // real mode: load the conversation + listen for incoming when a chat is open
  useAE(() => {
    if (demoMode || !authUser || !msgModal) return;
    Messages.get(authUser.id, msgModal).then(({ messages: msgs }) => {
      setMessages(prev => ({ ...prev, [msgModal]: (msgs || []).map(m => ({ ...m, status: "seen" })) }));
    });
    Messages.markRead(authUser.id, msgModal).then(() => {
      // re-fetch accurate unread count after marking read
      Messages.unreadCount(authUser.id).then(({ count }) => setUnreadCount(count || 0));
    });
    const unsub = Messages.subscribeToThread(authUser.id, msgModal, (row) => {
      setMessages(prev => ({ ...prev, [msgModal]: [...(prev[msgModal] || []), { id: row.id, from: "them", text: row.body, photo: row.media_url || null, status: "seen", time: row.created_at }] }));
      Messages.markRead(authUser.id, msgModal);
    });
    return unsub;
  }, [msgModal, authUser, demoMode]);

  // ── verification handlers ─────────────────────────────────
  const handleVerifySubmit = (payload) => {
    const { idUrl, selfieUrl } = payload || {};
    setShowIDVerify(false);
    if (demoMode) {
      setDemoVerify("pending");
      setRejectReason("");
      // surface my own submission in the admin queue so the flow is testable
      setPendingSubs(s => [
        { id: "__me", name: currentProfile.name || "You", handle: "@you", city: currentProfile.city || "", phone: "+91 90••• ••000", idPhotoUrl: idUrl || null, selfieUrl: selfieUrl || null },
        ...s.filter(x => x.id !== "__me"),
      ]);
    } else {
      setAuthProfile(p => ({ ...p, verificationStatus: "pending", rejectReason: null }));
    }
    showToast("Submitted for review — we'll verify you shortly.");
  };

  // load pending submissions + users when entering the admin screen (real mode)
  useAE(() => {
    if (demoMode || view !== "admin") return;
    setAdminLoading(true);
    Promise.all([Verification.listPending(), Admin.allUsers()]).then(([p, u]) => {
      setPendingSubs(p.subs || []);
      setAdminUsers(u.users || []);
      setAdminLoading(false);
    });
  }, [view, demoMode]);

  const approveSub = async (sub) => {
    if (!demoMode) await Verification.setStatus(sub.id, "verified");
    setPendingSubs(s => s.filter(x => x.id !== sub.id));
    if (sub.id === "__me") setDemoVerify("verified");
    if (!demoMode && sub.id === authUser?.id) setAuthProfile(p => ({ ...p, verificationStatus: "verified" }));
    showToast(`${sub.name} approved ✓`);
  };

  const rejectSub = async (sub, reason) => {
    if (!demoMode) await Verification.setStatus(sub.id, "rejected", reason);
    setPendingSubs(s => s.filter(x => x.id !== sub.id));
    if (sub.id === "__me") { setDemoVerify("rejected"); setRejectReason(reason || ""); }
    showToast(`${sub.name}'s submission was rejected.`);
  };

  const removeTask = async (taskId) => {
    if (!demoMode) await Admin.deleteTask(taskId);
    setRemovedIds(s => new Set(s).add(taskId));
    showToast("Task removed.");
  };

  const adminRefund = async (taskId) => {
    if (!demoMode) await Tasks.updateStatus(taskId, "cancelled");
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), status: "cancelled" } }));
    showToast("Marked as refunded.");
  };

  const adminRelease = async (taskId) => {
    const meta = getMeta(taskId);
    const proof = meta.proof || "Released by admin.";
    if (!demoMode) await Assignments.releasePayment(taskId, proof);
    setTaskMeta(prev => ({ ...prev, [taskId]: { ...getMeta(taskId), status: "completed", proof } }));
    fireConfetti();
    showToast("Payment released.");
  };

  const markPaidOut = (taskId) => {
    setPaidOut(s => new Set(s).add(taskId));
    showToast("Marked as paid out.");
  };

  const updateProfile = async (fields) => {
    if (!demoMode && authUser) {
      let avatarUrl;
      if (fields.photoFile) avatarUrl = (await Profiles.uploadAvatar(authUser.id, fields.photoFile)).url;
      const upd = { name: fields.name, city: fields.city };
      if (fields.phone !== undefined)           upd.phone = fields.phone || null;
      if (fields.bio !== undefined)             upd.bio = fields.bio;
      if (fields.skills !== undefined)          upd.skills = fields.skills;
      if (fields.availabilityDays !== undefined) upd.availability_days = fields.availabilityDays;
      if (fields.availabilityHours !== undefined) upd.availability_hours = fields.availabilityHours || null;
      if (fields.travelRadius !== undefined)    upd.travel_radius = fields.travelRadius || null;
      if (fields.vehicle !== undefined)         upd.has_vehicle = fields.vehicle || null;
      if (avatarUrl)                            upd.avatar_url = avatarUrl;
      await Profiles.upsert(authUser.id, upd);
      setAuthProfile(p => ({ ...(p || {}), name: fields.name, city: fields.city,
        ...(fields.phone !== undefined ? { phone: fields.phone } : {}),
        ...(fields.bio !== undefined ? { bio: fields.bio } : {}),
        ...(fields.skills !== undefined ? { skills: fields.skills } : {}),
        ...(fields.availabilityDays !== undefined ? { availabilityDays: fields.availabilityDays } : {}),
        ...(fields.availabilityHours !== undefined ? { availabilityHours: fields.availabilityHours } : {}),
        ...(fields.travelRadius !== undefined ? { travelRadius: fields.travelRadius } : {}),
        ...(fields.vehicle !== undefined ? { vehicle: fields.vehicle } : {}),
        ...(avatarUrl ? { avatarUrl } : {}) }));
    } else {
      setDemoProfile(p => {
        const next = { ...p, name: fields.name, city: fields.city };
        if (fields.phone !== undefined)           next.phone = fields.phone;
        if (fields.bio !== undefined)             next.bio = fields.bio;
        if (fields.skills !== undefined)          next.skills = fields.skills;
        if (fields.availabilityDays !== undefined) next.availabilityDays = fields.availabilityDays;
        if (fields.availabilityHours !== undefined) next.availabilityHours = fields.availabilityHours;
        if (fields.travelRadius !== undefined)    next.travelRadius = fields.travelRadius;
        if (fields.vehicle !== undefined)         next.vehicle = fields.vehicle;
        if (fields.photoPreview)                  next.avatarUrl = fields.photoPreview;
        return next;
      });
    }
    showToast("Profile updated.");
  };

  // ── ID verification prompt ────────────────────────────────
  const needsVerification = verification === "unverified";

  // ── apply tweaks ──────────────────────────────────────────
  useAE(() => {
    const r = document.documentElement;
    r.style.setProperty("--clay", t.accent);
    r.style.setProperty("--clay-dk", shade(t.accent, -0.16));
    r.style.setProperty("--display", `"${t.displayFont}", Georgia, serif`);
    const rad = RADII[t.radius] || RADII.soft;
    r.style.setProperty("--r-card", rad.card);
    r.style.setProperty("--r-btn", rad.btn);
    document.body.classList.toggle("dark", !!t.dark);
  }, [t]);

  // ── render ────────────────────────────────────────────────

  // full-screen loading on auth check
  if (authLoading) {
    return (
      <div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", background: "var(--paper)" }}>
        <div style={{ fontFamily: "var(--display)", fontSize: 20, color: "var(--ink-2)" }}>Loading…</div>
      </div>
    );
  }

  // after clicking a password-reset link → set a new password
  if (recoveryMode) {
    return (
      <ResetPasswordScreen onDone={() => {
        setRecoveryMode(false);
        Auth.getSession().then(({ session }) => { if (session?.user) ensureProfile(session.user); });
        showToast("Password updated — you're signed in.");
      }} />
    );
  }

  // not signed in views gating
  const AUTH_REQUIRED_VIEWS = ["post", "dashboard", "saved", "messages", "profile", "verify"];
  const requiresAuth = !demoMode && !authUser && AUTH_REQUIRED_VIEWS.includes(view);

  // first-time sign-in — collect name, city, role
  if (!demoMode && showOnboarding) {
    return (
      <OnboardingForm
        profile={authProfile}
        onComplete={async (fields) => {
          if (authUser) {
            let avatarUrl = null;
            if (fields.photoFile) avatarUrl = (await Profiles.uploadAvatar(authUser.id, fields.photoFile)).url;
            await Profiles.upsert(authUser.id, {
              name: fields.name,
              city: fields.city,
              phone: fields.phone || null,
              role: fields.role,
              preferred_categories: fields.preferredCategories,
              skills: fields.skills,
              availability_days: fields.availabilityDays,
              availability_hours: fields.availabilityHours || null,
              travel_radius: fields.travelRadius || null,
              has_vehicle: fields.vehicle || null,
              bio: fields.bio || null,
              ...(avatarUrl ? { avatar_url: avatarUrl } : {}),
            });
            setAuthProfile(p => ({ ...p, name: fields.name, city: fields.city, bio: fields.bio, avatarUrl: avatarUrl || p?.avatarUrl }));
          }
          setShowOnboarding(false);
        }}
      />
    );
  }

  // ── admin console — its own full-screen, gated experience (no consumer nav/footer) ──
  if (view === "admin") {
    if (!isAdmin) {
      return (
        <div style={{ minHeight: "100vh", display: "grid", placeItems: "center", background: "var(--paper)", padding: 24 }}>
          <div style={{ textAlign: "center", maxWidth: 380 }}>
            <div style={{ width: 64, height: 64, borderRadius: "50%", background: "var(--ink-3)", color: "var(--ink-2)", display: "grid", placeItems: "center", margin: "0 auto 18px" }}>
              <Icon name="shield" size={30} />
            </div>
            <h1 style={{ fontFamily: "var(--display)", fontWeight: 600, fontSize: 26, letterSpacing: "-0.02em", margin: "0 0 8px", color: "var(--ink)" }}>Admins only</h1>
            <p style={{ fontSize: 15.5, color: "var(--ink-2)", lineHeight: 1.5, margin: "0 0 24px" }}>You don't have access to the admin console.</p>
            <Button icon="back" onClick={() => go("home")}>Back to app</Button>
          </div>
        </div>
      );
    }
    return (
      <>
        <AdminPanel go={go} data={{
          stats: {
            totalUsers: adminData.users.length,
            pendingVerifications: pendingSubs.length,
            totalTransactions: adminData.payouts.length,
            revenue: adminData.payouts.reduce((s, p) => s + (p.amount || 0), 0),
            revenueCur: "₹",
          },
          verifications: adminVerifications,
          onApprove: approveSub,
          onReject: rejectSub,
          tasks: allTasks,
          getMeta,
          onRelease: adminRelease,
          onRefund: adminRefund,
        }} />
        {/* <SetupStatus /> */}
        {toast && (
          <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", zIndex: 200, background: "var(--ink)", color: "var(--paper)", padding: "14px 22px", borderRadius: 999, fontFamily: "var(--sans)", fontSize: 15, fontWeight: 600, display: "flex", alignItems: "center", gap: 10, boxShadow: "0 16px 40px -12px rgba(20,14,6,.5)", whiteSpace: "nowrap" }}>
            <span style={{ color: "var(--green)" }}><Icon name="check" size={18} stroke={2.6} /></span> {toast}
          </div>
        )}
      </>
    );
  }

  const task = params.taskId ? allTasks.find(x => x.id === params.taskId) : null;

  return (
    <div>
      {/* <Confetti trigger={confetti} /> */}
      {/* <SetupStatus /> */}
      {!requiresAuth && (
        <Nav go={go} view={view} onPost={() => go("post")} savedCount={saved.size}
          unreadCount={unreadCount} isAdmin={isAdmin}
          notifications={notifications} notifUnread={notifUnread} onNotifOpen={markNotifsRead} onNotifClick={openNotif}
          canLogout={!demoMode && !!authUser} onLogout={logout}
          profile={currentProfile} email={authUser?.email} />
      )}
      <main>
        {requiresAuth ? (
          <AuthGate demoMode={false} onAuth={(user, profile) => { setAuthUser(user); setAuthProfile(profile); }} backUrl="#home" backLabel="Back to home" />
        ) : (
          <>
            {view === "home" && <Landing go={go} openTask={openTask} onPost={() => go("post")} tasks={allTasks.filter(t => taskStatus(t) !== "completed" && taskStatus(t) !== "cancelled")} />}

            {view === "browse" && (
              <Browse go={go} openTask={openTask} tasks={allTasks.filter(t => taskStatus(t) !== "completed" && taskStatus(t) !== "cancelled")}
                initialCat={params.cat} saved={saved} toggleSave={toggleSave} userCity={currentProfile.city} />
            )}

            {view === "how" && (
              <div style={{ paddingTop: 20 }}>
                <HowItWorks go={go} onPost={() => go("post")} />
                <TrustStrip />
                <div style={{ height: 60 }} />
              </div>
            )}

            {view === "saved" && (
              <SavedTasks go={go} openTask={openTask}
                tasks={allTasks.filter(t => saved.has(t.id))}
                saved={saved} toggleSave={toggleSave} />
            )}

            {view === "task" && task && (
              <TaskDetail
                task={task} go={go} openTask={openTask} allTasks={allTasks}
                currentUserId={currentUserId}
                applied={appliedIds.has(task.id)} onApply={apply}
                taskMeta={getMeta(task.id)}
                taskApplicants={taskApps[task.id] || []}
                onAccept={app => acceptApp(task.id, app)}
                onReject={appId => rejectApp(task.id, appId)}
                onRelease={proof => releasePayment(task.id, proof)}
                onReview={review => leaveReview(task.id, review)}
                onSubmitProof={submitProof}
                onMessage={openMessage}
                onViewProfile={openProfile}
                saved={saved.has(task.id)}
                onSave={toggleSave}
                verification={verification}
                onNeedVerify={() => go("verify")}
                onBoost={(newAmount) => boostBudget(task.id, newAmount)}
                onCancel={(reason) => cancelTask(task.id, reason)}
                onReviewPoster={(review) => reviewPoster(task.id, review)}
                onWithdraw={() => withdrawTask(task.id)}
                onEdit={(fields) => editTask(task.id, fields)}
              />
            )}

            {view === "profile" && (
              <ProfilePage go={go} onLogout={logout} verificationStatus={verification} rejectReason={myRejectReason}
                user={currentProfile} email={authUser?.email} onSave={updateProfile} />
            )}

            {view === "verify" && (
              <VerifyFlow go={go} onComplete={async (data) => {
                const last4 = (data?.aadhaarNumber || "").replace(/\D/g, "").slice(-4) || null;
                if (demoMode) {
                  setDemoVerify("pending");
                  setRejectReason("");
                  setPendingSubs(s => [{
                    id: "__me", name: currentProfile.name || "You", handle: "@you", city: currentProfile.city || "",
                    phone: data?.phone || "", aadhaarName: data?.aadhaarName || "", aadhaarLast4: last4, upiId: data?.upiId || "",
                    idPhotoUrl: data?.idPreview || null, selfieUrl: data?.selfiePreview || null, videoUrl: data?.videoPreview || null,
                  }, ...s.filter(x => x.id !== "__me")]);
                } else if (authUser) {
                  let idPath = null, selfiePath = null, videoPath = null;
                  if (data?.idFile) idPath = (await Profiles.uploadDoc(authUser.id, data.idFile, "id")).path;
                  if (data?.selfieFile) selfiePath = (await Profiles.uploadDoc(authUser.id, data.selfieFile, "selfie")).path;
                  if (data?.videoFile) videoPath = (await Profiles.uploadDoc(authUser.id, data.videoFile, "video")).path;
                  await Profiles.upsert(authUser.id, {
                    phone: data?.phone || null,
                    id_photo_url: idPath, selfie_url: selfiePath, video_url: videoPath,
                    aadhaar_name: data?.aadhaarName || null, aadhaar_last4: last4, upi_id: data?.upiId || null,
                    verification_status: "pending", reject_reason: null,
                  });
                  setAuthProfile(p => ({ ...(p || {}), verificationStatus: "pending", rejectReason: null }));
                }
                showToast("Verification submitted — we'll review it shortly.");
                notify({ type: "verified", title: "Verification under review", body: "We've received your documents — you'll hear back within 24h." });
                go("profile");
              }} />
            )}

            {view === "messages" && (
              <InboxPage
                go={go} messages={messages} openMessage={openMessage}
                authUser={authUser} demoMode={demoMode}
              />
            )}

            {view === "legal" && <LegalPage go={go} doc={params.doc} />}

            {view === "user" && (
              <PublicProfile
                go={go} userId={params.userId} demoMode={demoMode}
                onMessage={openMessage} currentUserId={currentUserId}
                verification={verification} onNeedVerify={() => go("verify")}
              />
            )}

            {view === "wallet" && (
              <WalletPage go={go} userId={currentUserId} currentProfile={currentProfile} demoMode={demoMode} refreshWallet={refreshWallet} fireConfetti={fireConfetti} />
            )}

            {view === "post" && <PostTask go={go} onPublish={publish} userEmail={authUser?.email || ""} userName={currentProfile?.name || ""} />}

            {view === "dashboard" && (
              <Dashboard go={go} openTask={openTask}
                posted={demoMode ? extraTasks : myPosted}
                applications={applications}
                allTasks={allTasks}
                taskMeta={taskMeta} getMeta={getMeta}
                saved={saved} toggleSave={toggleSave}
                profile={currentProfile}
                onVerify={() => go("verify")}
                canLogout={!demoMode && !!authUser} onLogout={logout}
                walletBalance={walletBalance} walletCur={walletCur}
              />
            )}

            {view === "settings" && (
              <SettingsPage
                go={go} profile={currentProfile} email={authUser?.email}
                demoMode={demoMode} verification={verification} rejectReason={myRejectReason}
                onSave={updateProfile} onVerify={() => go("verify")}
                onLogout={logout} canLogout={!demoMode && !!authUser}
                dark={t.dark} onToggleDark={(v) => setTweak("dark", v)}
              />
            )}
          </>
        )}
      </main>
      {!requiresAuth && <Footer go={go} isAdmin={isAdmin} />}

      {/* toast */}
      {toast && (
        <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", zIndex: 200, background: "var(--ink)", color: "var(--paper)", padding: "14px 22px", borderRadius: 999, fontFamily: "var(--sans)", fontSize: 15, fontWeight: 600, display: "flex", alignItems: "center", gap: 10, boxShadow: "0 16px 40px -12px rgba(20,14,6,.5)", animation: "toastIn .3s ease", whiteSpace: "nowrap" }}>
          <span style={{ color: "var(--green)" }}><Icon name="check" size={18} stroke={2.6} /></span> {toast}
        </div>
      )}

      {/* message modal */}
      {msgModal && (
        <MessageModal
          theirHandle={msgModal}
          theirProfile={msgProfile}
          messages={messages[msgModal] || []}
          onSend={text => sendMessage(msgModal, text)}
          onClose={() => { setMsgModal(null); setMsgProfile(null); }}
        />
      )}

      {/* ID verification modal */}
      {showIDVerify && (
        <IDVerifyModal
          userId={currentUserId}
          onClose={() => setShowIDVerify(false)}
          onSubmit={handleVerifySubmit}
        />
      )}

      {/* banned banner */}
      {currentProfile?.banned && (
        <div style={{ position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 60, background: "var(--clay)", padding: "14px 24px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, flexWrap: "wrap", color: "#fff" }}>
          <span style={{ fontFamily: "var(--sans)", fontWeight: 600, fontSize: 14.5 }}>
            <Icon name="ban" size={16} style={{ verticalAlign: "middle", marginRight: 6, marginBottom: 2 }} />
            Your account has been permanently banned due to multiple strikes.
          </span>
          <Button size="sm" variant="ghost" onClick={() => window.location.href = "mailto:hiringahuman@gmail.com"} style={{ color: "#fff", borderColor: "color-mix(in srgb, #fff 35%, transparent)" }}>Contact Support</Button>
        </div>
      )}

      {/* unverified banner */}
      {needsVerification && !currentProfile?.banned && view === "dashboard" && (
        <div style={{ position: "fixed", bottom: 0, left: 0, right: 0, zIndex: 50, background: "var(--amber)", padding: "14px 24px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14, flexWrap: "wrap" }}>
          <span style={{ fontFamily: "var(--sans)", fontWeight: 600, fontSize: 14.5, color: "#2a1800" }}>
            Verify your identity to apply for paid tasks.
          </span>
          <Button size="sm" variant="dark" icon="shield" onClick={() => go("verify")}>Verify now</Button>
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Accent" />
        <TweakColor label="Primary color" value={t.accent}
          options={["#DC4B2E", "#3B5BDB", "#1F7A55", "#7A4DD0", "#0E7C86"]}
          onChange={v => setTweak("accent", v)} />
        <TweakSection label="Type" />
        <TweakSelect label="Display font" value={t.displayFont}
          options={["Bricolage Grotesque", "Instrument Serif", "Space Grotesk", "Fraunces"]}
          onChange={v => setTweak("displayFont", v)} />
        <TweakSection label="Shape" />
        <TweakRadio label="Corners" value={t.radius} options={["sharp", "soft", "round"]} onChange={v => setTweak("radius", v)} />
        <TweakSection label="Theme" />
        <TweakToggle label="Dark mode" value={t.dark} onChange={v => setTweak("dark", v)} />
      </TweaksPanel>
      {/* confetti */}
      <Confetti active={confetti} />
    </div>
  );
}

/* ── Setup / connection status badge ──────────────────────── */
function SetupStatus() {
  const [status, setStatus] = useA(null);
  const [hidden, setHidden] = useA(false);

  useAE(() => {
    let alive = true;
    Health.check().then(r => { if (alive) setStatus(r); });
    return () => { alive = false; };
  }, []);

  // auto-hide the "connected" + "demo" states after a few seconds
  useAE(() => {
    if (status && (status.state === "connected" || status.state === "demo")) {
      const t = setTimeout(() => setHidden(true), 4500);
      return () => clearTimeout(t);
    }
  }, [status]);

  if (!status || hidden) return null;

  const map = {
    demo:       { color: "var(--ink-2)", bg: "var(--ink-3)", dot: "var(--ink-2)", title: "Demo mode", detail: "No database — changes reset on refresh. Add keys in config.jsx to go live." },
    connected:  { color: "var(--green)", bg: "var(--green-3)", dot: "var(--green)", title: "Database connected", detail: "Supabase is live. You're good to go." },
    "no-schema":{ color: "var(--amber-dk)", bg: "color-mix(in srgb, var(--amber) 16%, transparent)", dot: "var(--amber)", title: "Connected — tables missing", detail: "Run schema.sql in Supabase → SQL Editor." },
    "no-bucket":{ color: "var(--amber-dk)", bg: "color-mix(in srgb, var(--amber) 16%, transparent)", dot: "var(--amber)", title: "Tables ready — storage missing", detail: "Re-run the storage section of schema.sql." },
    "bad-key":  { color: "var(--clay)", bg: "color-mix(in srgb, var(--clay) 12%, transparent)", dot: "var(--clay)", title: "Invalid keys", detail: "Check SUPABASE_URL / ANON key in config.jsx." },
    error:      { color: "var(--clay)", bg: "color-mix(in srgb, var(--clay) 12%, transparent)", dot: "var(--clay)", title: "Can't reach Supabase", detail: status.detail || "Check your connection and keys." },
  };
  const s = map[status.state] || map.error;

  return (
    <div style={{ position: "fixed", left: 16, bottom: 16, zIndex: 220, maxWidth: 320, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14, boxShadow: "0 12px 30px -14px rgba(20,14,6,.4)", padding: "12px 14px", display: "flex", gap: 11, alignItems: "flex-start" }}>
      <span style={{ width: 9, height: 9, borderRadius: "50%", background: s.dot, flexShrink: 0, marginTop: 5, boxShadow: `0 0 0 4px ${s.bg}` }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 700, color: s.color, fontFamily: "var(--sans)" }}>{s.title}</div>
        <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.4, marginTop: 2 }}>{s.detail}</div>
      </div>
      <button onClick={() => setHidden(true)} style={{ background: "none", border: "none", cursor: "pointer", color: "var(--ink-2)", fontSize: 14, padding: 0, lineHeight: 1, flexShrink: 0 }}>✕</button>
    </div>
  );
}

function shade(hex, amt) {
  const h = hex.replace("#", "");
  const n = parseInt(h.length === 3 ? h.split("").map(c => c + c).join("") : h, 16);
  let r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
  r = Math.max(0, Math.min(255, Math.round(r + r * amt)));
  g = Math.max(0, Math.min(255, Math.round(g + g * amt)));
  b = Math.max(0, Math.min(255, Math.round(b + b * amt)));
  return "#" + [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");
}

// catches any render crash so a single bad component can't white-screen the whole app
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) { console.error("App crashed:", error, info); }
  render() {
    if (!this.state.error) return this.props.children;
    return (
      <div style={{ minHeight: "100vh", display: "grid", placeItems: "center", background: "var(--paper)", padding: 24, fontFamily: "var(--sans)" }}>
        <div style={{ textAlign: "center", maxWidth: 460 }}>
          <div style={{ fontSize: 44, marginBottom: 14 }}>😵</div>
          <h1 style={{ fontFamily: "var(--display)", fontWeight: 600, fontSize: 26, letterSpacing: "-0.02em", margin: "0 0 8px", color: "var(--ink)" }}>Something went wrong</h1>
          <p style={{ fontSize: 15, color: "var(--ink-2)", lineHeight: 1.5, margin: "0 0 20px" }}>The app hit an unexpected error. Reloading usually fixes it.</p>
          <button onClick={() => location.reload()} style={{ padding: "12px 24px", borderRadius: 999, border: "none", background: "var(--clay)", color: "#fff", fontWeight: 700, fontSize: 15, cursor: "pointer", fontFamily: "var(--sans)" }}>Reload</button>
          <pre style={{ marginTop: 18, textAlign: "left", fontSize: 11.5, color: "var(--ink-2)", background: "var(--ink-3)", padding: "10px 12px", borderRadius: 10, overflow: "auto", maxHeight: 120, whiteSpace: "pre-wrap" }}>{String(this.state.error?.message || this.state.error)}</pre>
        </div>
      </div>
    );
  }
}

ReactDOM.createRoot(document.getElementById("root")).render(<ErrorBoundary><App /></ErrorBoundary>);
