// app.jsx — application shell: auth, RBAC routing, state, workflow overrides, tweaks
const { useState: useS, useEffect: useE, useMemo: useM } = React;

const ACCENTS = {
  teal:   { "--accent": "#0d9488", "--accent-bright": "#2dd4bf", "--accent-wash": "#f0fdfa", "--accent-border": "#99f6e4", "--accent-ink": "#0f766e" },
  indigo: { "--accent": "#4f46e5", "--accent-bright": "#818cf8", "--accent-wash": "#eef2ff", "--accent-border": "#c7d2fe", "--accent-ink": "#4338ca" },
  blue:   { "--accent": "#2563eb", "--accent-bright": "#60a5fa", "--accent-wash": "#eff6ff", "--accent-border": "#bfdbfe", "--accent-ink": "#1d4ed8" },
  rose:   { "--accent": "#e11d48", "--accent-bright": "#fb7185", "--accent-wash": "#fff1f2", "--accent-border": "#fecdd3", "--accent-ink": "#be123c" },
};
const SIDE_TONES = { black: "#0a0a0a", slate: "#0f172a", stone: "#1c1917" };

const PAGE_META = {
  dashboard:    { title: "Operations Dashboard", sub: "Real-time exception monitoring · Shah Alam DC" },
  queue:        { title: "Exception Queue", sub: "All flagged orders requiring action" },
  assigned:     { title: "Assigned Exceptions", sub: "Exceptions routed to you" },
  tasks:        { title: "My Tasks", sub: "Your personal action list" },
  orders:       { title: "Orders", sub: "All orders across channels" },
  import:       { title: "Import Orders", sub: "Sync Shopify or upload a CSV" },
  automations:  { title: "Automations", sub: "Workflow rules & the auto-resolution agent" },
  analytics:    { title: "Operational Analytics", sub: "Resolution, SLA & warehouse performance" },
  users:        { title: "Users & Access", sub: "Manage team members and roles" },
  integrations: { title: "Integrations", sub: "Connected systems & data sources" },
  rules:        { title: "Admin Rules", sub: "Configure exception detection logic" },
  audit:        { title: "Audit Logs", sub: "System-wide activity trail" },
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "teal",
  "density": "comfortable",
  "sidebarTone": "black"
}/*EDITMODE-END*/;

function ToastStack({ toasts }) {
  return (
    <div className="fixed bottom-5 right-5 z-[60] flex flex-col gap-2 items-end">
      {toasts.map(t => (
        <div key={t.id} className="flex items-center gap-2.5 bg-zinc-900 text-white rounded-xl pl-3 pr-4 py-2.5 shadow-lg animate-[toastin_0.25s_ease]">
          <span className="flex items-center justify-center w-5 h-5 rounded-full shrink-0" style={{ background: t.tone === "danger" ? "#ef4444" : t.tone === "warn" ? "#f59e0b" : "#10b981" }}>
            <Icon name={t.icon || "check"} size={12} strokeWidth={2.5} />
          </span>
          <span className="text-[13px] font-medium">{t.msg}</span>
        </div>
      ))}
    </div>
  );
}

// ── Per-page contextual tour ──────────────────────────────────────────────────

const PAGE_TOUR_KEY = (page) => `trapo_ptour_v1_${page}`;

// Layout constants
const SW = 232, TH = 64, PW = 388;

// Spotlight positions — subtle pulsing border only, no dark backdrop
const S = {
  kpiRow:      { top: TH+16,  left: SW+16, right: 16,     height: 118 },
  insights:    { top: TH+150, left: SW+16, right: 16,     height: 168 },
  tableArea:   { top: TH+334, left: SW+16, right: PW+16,  bottom: 16  },
  tableNoPnl:  { top: TH+200, left: SW+16, right: 16,     bottom: 16  },
  statStrip:   { top: TH+16,  left: SW+16, right: 16,     height: 86  },
  filterBar:   { top: TH+112, left: SW+16, right: 16,     height: 52  },
  tableQ:      { top: TH+176, left: SW+16, right: PW+16,  bottom: 16  },
  rightPanel:  { top: TH+12,  right: 12,   width: PW-24,  bottom: 12  },
  fullMain:    { top: TH+16,  left: SW+16, right: 16,     bottom: 16  },
  topHalf:     { top: TH+16,  left: SW+16, right: 16,     height: 260 },
  btmHalf:     { top: TH+290, left: SW+16, right: 16,     bottom: 16  },
};

// Card anchor positions (fixed on screen, avoid overlapping spotlight)
const CP = {
  tr:   { top: TH+16,  right: 16  },
  tl:   { top: TH+16,  left: SW+16 },
  br:   { bottom: 72,  right: 16  },
  bl:   { bottom: 72,  left: SW+16 },
  lp:   { top: "50%",  right: PW+16, transform: "translateY(-50%)" },
  bc:   { bottom: 72,  left: "50%", transform: "translateX(-50%)" },
  ctr:  { top: "50%",  left: "50%", transform: "translate(-50%,-50%)" },
};

// ── Page tour content (role-aware where needed) ───────────────────────────────
// Each step: { icon, title, body, spot: S.xxx | null, card: CP.xxx }

function buildPageTours(role) {
  const canAct   = role !== "viewer";
  const isStaff  = role === "ops_staff";
  const isViewer = role === "viewer";

  return {

    dashboard: [
      {
        icon: "home",
        title: "KPI Cards",
        body: "Live snapshot of queue health. Green delta = improving, red = worsening. Numbers update every 60 seconds.",
        spot: S.kpiRow,
        card: CP.br,
      },
      {
        icon: "ai",
        title: "AI Operational Insights",
        body: "4 pattern observations auto-generated from your order data. Click any card to jump to the relevant page.",
        spot: S.insights,
        card: CP.tr,
      },
      {
        icon: "flag",
        title: "Today's Exceptions Table",
        body: canAct
          ? "Flagged orders needing action. Click any row to open the detail panel — AI summary, risk score, and reply tools are all there."
          : "Flagged orders. Click any row to read the AI summary and order timeline (read-only).",
        spot: S.tableArea,
        card: CP.tl,
      },
    ],

    queue: [
      {
        icon: "flag",
        title: "Queue Stats",
        body: "At a glance: total open, high-priority count, unassigned, and how many have breached the 48h SLA.",
        spot: S.statStrip,
        card: CP.br,
      },
      {
        icon: "filter",
        title: "Filter & Sort",
        body: "Narrow by Priority, Status, Issue type, or Assignee. Sort by Waiting Time to surface the most urgent cases first.",
        spot: S.filterBar,
        card: CP.br,
      },
      {
        icon: "box",
        title: "Exception Rows",
        body: "Click any row to open the detail panel. The AI Risk chip (score/100) shows urgency. Red waiting time = SLA breached.",
        spot: S.tableQ,
        card: CP.tl,
      },
      {
        icon: "chat",
        title: "Detail Panel",
        body: "AI summary, priority score, assign to staff, generate a customer reply (WhatsApp / Email / Internal), then Escalate or Mark Resolved.",
        spot: S.rightPanel,
        card: CP.lp,
      },
    ],

    assigned: [
      {
        icon: "flag",
        title: "Your Assigned Queue",
        body: "Only exceptions routed to you by your manager. The badge in the sidebar shows how many are waiting.",
        spot: S.tableNoPnl,
        card: CP.tr,
      },
      {
        icon: "box",
        title: "Open an Exception",
        body: "Click any row. The panel on the right shows the AI summary, full timeline, and the recommended next action.",
        spot: S.tableQ,
        card: CP.tl,
      },
      {
        icon: "chat",
        title: "Reply, then Resolve",
        body: "① Pick channel (WhatsApp / Email / Internal) ② Choose tone ③ Generate AI reply ④ Send ⑤ Click Mark Resolved or Escalate at the bottom.",
        spot: S.rightPanel,
        card: CP.lp,
      },
    ],

    tasks: [
      {
        icon: "calendar",
        title: "Task Groups",
        body: "Tasks are grouped by status: To Do, In Progress, Done. Each task links to a specific order and was assigned by your manager.",
        spot: S.topHalf,
        card: CP.br,
      },
      {
        icon: "check",
        title: "Start & Complete",
        body: "Click Start to move a task to In Progress. Click Complete when done. Overdue tasks (past due date) appear with a red badge.",
        spot: S.btmHalf,
        card: CP.tr,
      },
    ],

    import: [
      {
        icon: "truck",
        title: "Two Import Sources",
        body: "Shopify Storefront — syncs new orders automatically. CSV Upload — drag and drop a .csv file if you have offline orders.",
        spot: S.topHalf,
        card: CP.br,
      },
      {
        icon: "alert",
        title: "Anomaly Detection",
        body: "After import, OpsPilot scans for issues: missing tracking, delayed fulfillment, wrong car model mapping, duplicate warranty claims.",
        spot: S.btmHalf,
        card: CP.tr,
      },
      {
        icon: "plus",
        title: "Add to Exception Queue",
        body: "Review detected anomalies in the table, then click 'Add N to queue' to convert them into actionable exceptions.",
        spot: S.fullMain,
        card: CP.tl,
      },
    ],

    automations: [
      {
        icon: "zap",
        title: "Workflow Builder",
        body: "Build WHEN → IF → THEN rules. Example: when an order is delayed 48h AND value ≥ RM200, auto-assign to Warehouse Manager + raise priority.",
        spot: S.topHalf,
        card: CP.br,
      },
      {
        icon: "ai",
        title: "Auto-Resolution Agent",
        body: "The AI agent scores each exception (0–100 confidence). Use the threshold slider to set what gets auto-resolved vs routed to a human.",
        spot: S.btmHalf,
        card: CP.tr,
      },
    ],

    analytics: [
      {
        icon: "chart",
        title: "KPI Tiles + Range",
        body: "Avg resolution time, SLA compliance, total resolved, and auto-resolution rate. Use 7d / 14d / 30d to change the period. Export downloads a CSV.",
        spot: S.statStrip,
        card: CP.br,
      },
      {
        icon: "home",
        title: "Charts",
        body: "Resolution trend (area) · Issue breakdown (donut — hover a slice to highlight) · Warehouse performance (bars) · SLA gauge with per-category bars.",
        spot: S.fullMain,
        card: CP.tl,
      },
    ],

    users: [
      {
        icon: "users",
        title: "User Roster",
        body: "Active, Invited, and Disabled accounts. Role badge shows permissions. 'Last active' helps spot inactive accounts.",
        spot: S.tableNoPnl,
        card: CP.tr,
      },
      {
        icon: "user",
        title: "Manage Users",
        body: "Click Invite User to add someone. Use the ⋮ row menu to change role, send a password reset, or disable an account.",
        spot: S.filterBar,
        card: CP.br,
      },
    ],

    integrations: [
      {
        icon: "link",
        title: "Connected Systems",
        body: "Each card shows connection status and last sync time. Green = connected, yellow = warning, red = action needed.",
        spot: S.topHalf,
        card: CP.br,
      },
      {
        icon: "alert",
        title: "Fixing Errors",
        body: "Click Fix on an error card to reconnect. Disconnected integrations (like Xero) can be connected from here when ready.",
        spot: S.btmHalf,
        card: CP.tr,
      },
    ],

    rules: [
      {
        icon: "flag",
        title: "Rule Cards",
        body: "Each rule defines when an exception is triggered — e.g. 'flag paid orders not fulfilled after 24h'. Adjust thresholds with the +/− stepper.",
        spot: S.topHalf,
        card: CP.br,
      },
      {
        icon: "check",
        title: "Save Changes",
        body: "Editing a rule marks it dirty (blue border). Click Save on that card — changes take effect immediately, not on next refresh.",
        spot: S.btmHalf,
        card: CP.tr,
      },
    ],

    audit: [
      {
        icon: "shield",
        title: "Activity Trail",
        body: "Every system action is recorded here: rule edits, assignments, AI reply generation, user invites, integration events, and login activity.",
        spot: S.tableNoPnl,
        card: CP.tr,
      },
      {
        icon: "filter",
        title: "Filter & Export",
        body: "Filter by event type (Rule, AI, Assignment, Auth…) or search by actor name. Export CSV for compliance reporting.",
        spot: S.filterBar,
        card: CP.br,
      },
    ],

    orders: [
      {
        icon: "box",
        title: "All Orders",
        body: "Full order history across all channels. Status chips show Delivered / Shipped / Packed / Paid / Exception.",
        spot: S.tableNoPnl,
        card: CP.tr,
      },
      {
        icon: "flag",
        title: "Exception Orders",
        body: "Rows with an Exception chip have an active issue. Click 'Resolve →' to jump straight to that order's detail panel.",
        spot: S.btmHalf,
        card: CP.tl,
      },
    ],

  };
}

// ── PageTour component ────────────────────────────────────────────────────────

function PageTour({ page, role, onDone }) {
  const tours  = buildPageTours(role);
  const steps  = tours[page];
  const [step, setS] = useS(0);

  if (!steps || steps.length === 0) return null;

  const cur    = steps[step];
  const total  = steps.length;
  const isLast = step === total - 1;

  function next()   { isLast ? finish() : setS(s => s + 1); }
  function finish() { localStorage.setItem(PAGE_TOUR_KEY(page), "1"); onDone(); }

  // Spotlight — accent border + large box-shadow creates the dark backdrop with cutout effect
  const spotStyle = cur.spot ? {
    position: "fixed", zIndex: 9050,
    borderRadius: 10,
    outline: "2px solid var(--accent)",
    outlineOffset: "2px",
    boxShadow: "0 0 0 9999px rgba(0,0,0,0.52)",
    pointerEvents: "none",
    animation: "pulse 1.6s ease-in-out infinite",
    transition: "top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease",
    ...cur.spot,
  } : null;

  // Tour card — elevated above the dark overlay
  const cardStyle = {
    position: "fixed", zIndex: 9100,
    width: 300,
    background: "#fff",
    borderRadius: 14,
    border: "1px solid #e4e4e7",
    boxShadow: "0 12px 40px rgba(0,0,0,0.28)",
    padding: "16px 18px",
    pointerEvents: "auto",
    ...cur.card,
  };

  return (
    <div style={{ position: "fixed", inset: 0, zIndex: 9000, pointerEvents: "none" }}>
      {/* dim backdrop for steps without a spotlight target */}
      {!cur.spot && (
        <div style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.48)", pointerEvents: "auto" }} onClick={finish} />
      )}

      {/* spotlight cutout */}
      {spotStyle && <div style={spotStyle} />}

      {/* tour card */}
      <div style={cardStyle}>
        {/* header */}
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
          <div style={{
            width: 28, height: 28, borderRadius: 7, flexShrink: 0,
            display: "flex", alignItems: "center", justifyContent: "center",
            background: "var(--accent-wash)", color: "var(--accent)",
          }}>
            <Icon name={cur.icon} size={14} />
          </div>
          <div style={{ flex: 1, fontSize: 13, fontWeight: 600, color: "#18181b" }}>{cur.title}</div>
          <button onClick={finish} style={{ background: "none", border: "none", cursor: "pointer", color: "#a1a1aa", padding: "2px 4px", fontSize: 17, lineHeight: 1, flexShrink: 0 }}>×</button>
        </div>

        {/* step dots */}
        <div style={{ display: "flex", gap: 3, marginBottom: 10 }}>
          {steps.map((_, i) => (
            <div key={i} style={{
              height: 3, borderRadius: 3,
              width: i === step ? 20 : 6,
              background: i === step ? "var(--accent)" : i < step ? "var(--accent-border)" : "#e4e4e7",
              transition: "width 0.2s, background 0.2s",
            }} />
          ))}
        </div>

        {/* body */}
        <div style={{ fontSize: 12.5, color: "#52525b", lineHeight: 1.6, marginBottom: 14 }}>
          {cur.body}
        </div>

        {/* footer */}
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          {step > 0 && (
            <button onClick={() => setS(s => s - 1)} style={{
              padding: "5px 11px", borderRadius: 7, border: "1px solid #e4e4e7",
              background: "#fff", color: "#52525b", fontSize: 12, cursor: "pointer",
            }}>← Back</button>
          )}
          <div style={{ flex: 1 }} />
          <button onClick={finish} style={{
            padding: "5px 11px", borderRadius: 7, border: "1px solid #e4e4e7",
            background: "#fff", color: "#71717a", fontSize: 12, cursor: "pointer",
          }}>Skip</button>
          <button onClick={next} style={{
            padding: "5px 14px", borderRadius: 7, border: "none",
            background: "var(--accent)", color: "#fff", fontSize: 12.5, fontWeight: 500, cursor: "pointer",
          }}>{isLast ? "Got it ✓" : `Next (${step + 1}/${total})`}</button>
        </div>
      </div>
    </div>
  );
}

// ── App ───────────────────────────────────────────────────────────────────────
// ── Set Password modal (shown after user clicks invite link) ──────────────────
function SetPasswordModal({ onDone }) {
  const [pw, setPw] = useS("");
  const [pw2, setPw2] = useS("");
  const [showPw, setShowPw] = useS(false);
  const [busy, setBusy] = useS(false);
  const [err, setErr] = useS(null);
  const valid = pw.length >= 8 && pw === pw2;

  async function save(e) {
    e?.preventDefault();
    if (!valid) return;
    setBusy(true);
    setErr(null);
    const { error } = await sb.auth.updateUser({ password: pw });
    if (error) { setErr(error.message); setBusy(false); return; }
    onDone();
  }

  return (
    <div className="fixed inset-0 z-[200] flex items-center justify-center bg-zinc-900/50 backdrop-blur-[2px]" style={{ fontFamily: "var(--font-ui)" }}>
      <form onSubmit={save} className="bg-white rounded-2xl shadow-2xl border border-zinc-200 w-full max-w-sm p-8">
        <div className="flex items-center justify-center w-12 h-12 rounded-xl mb-5 mx-auto" style={{ background: "var(--accent-wash)", color: "var(--accent)" }}>
          <Icon name="lock" size={22} />
        </div>
        <h2 className="text-[18px] font-semibold text-zinc-900 text-center tracking-tight">Set your password</h2>
        <p className="text-[13px] text-zinc-500 mt-1.5 text-center">Welcome to TRAPO OpsPilot. Create a password to get started.</p>

        {err && <div className="mt-4 px-3 py-2 rounded-lg bg-red-50 border border-red-200 text-[12.5px] text-red-700">{err}</div>}

        <label className="block mt-5">
          <span className="text-[12.5px] font-medium text-zinc-700">New password</span>
          <div className="flex items-center gap-2 mt-1.5 h-10 px-3 rounded-lg border border-zinc-300 focus-within:border-zinc-900 bg-white">
            <Icon name="lock" size={15} className="text-zinc-400" />
            <input type={showPw ? "text" : "password"} value={pw} onChange={e => setPw(e.target.value)}
              placeholder="Min. 8 characters" className="flex-1 bg-transparent outline-none text-[13.5px]" />
            <button type="button" onClick={() => setShowPw(s => !s)} className="text-zinc-400 hover:text-zinc-600">
              <Icon name={showPw ? "eyeoff" : "eye"} size={15} />
            </button>
          </div>
        </label>

        <label className="block mt-3">
          <span className="text-[12.5px] font-medium text-zinc-700">Confirm password</span>
          <div className="flex items-center gap-2 mt-1.5 h-10 px-3 rounded-lg border focus-within:border-zinc-900 bg-white"
            style={{ borderColor: pw2 && pw !== pw2 ? "#ef4444" : "" }}>
            <Icon name="lock" size={15} className="text-zinc-400" />
            <input type={showPw ? "text" : "password"} value={pw2} onChange={e => setPw2(e.target.value)}
              placeholder="Re-enter password" className="flex-1 bg-transparent outline-none text-[13.5px]" />
          </div>
          {pw2 && pw !== pw2 && <p className="mt-1 text-[11.5px] text-red-600">Passwords don't match</p>}
        </label>

        <button type="submit" disabled={!valid || busy}
          className="mt-6 w-full h-10 rounded-lg text-white text-[14px] font-semibold flex items-center justify-center gap-2 transition-all hover:brightness-110 disabled:opacity-50"
          style={{ background: "var(--accent)" }}>
          {busy ? <><Icon name="refresh" size={16} className="animate-spin" /> Saving…</> : "Set password & sign in"}
        </button>
      </form>
    </div>
  );
}

const SESSION_KEY = "trapo_session_v1";

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [session, setSession] = useS(() => { try { return JSON.parse(localStorage.getItem(SESSION_KEY)) || null; } catch { return null; } });
  const role = session?.role || null;
  const perms = role ? PERMS[role] : null;
  // use real profile from Supabase if available, fall back to mock ROLE_USER for demo
  const user = session?.profile
    ? { name: session.profile.name, initials: session.profile.initials, color: session.profile.color, role: ROLES[role]?.label }
    : role ? ROLE_USER[role] : null;

  const [page, setPage] = useS("dashboard");
  const [collapsed, setCollapsed] = useS(false);
  const [selectedId, setSelectedId] = useS(null);
  const [loading, setLoading] = useS(true);
  const [statusOv, setStatusOv] = useS({});
  const [staffOv, setStaffOv] = useS({});
  const [prioOv, setPrioOv] = useS({});
  const [wfAdds, setWfAdds] = useS({});
  const [toasts, setToasts] = useS([]);
  const [searchOpen, setSearchOpen] = useS(false);
  const [pageTour, setPageTour] = useS(false);
  const [needsPassword, setNeedsPassword] = useS(false);
  const [concurrentLogin, setConcurrentLogin] = useS(null); // { at } when another session logs in

  // concurrent-session channel ref — defined early so useE below can call it
  const sessionChannelRef = React.useRef(null);
  function subscribeConcurrentLogin(userId) {
    if (sessionChannelRef.current) sb.removeChannel(sessionChannelRef.current);
    const ch = sb.channel(`user-session-${userId}`);
    ch.on("broadcast", { event: "new_login" }, ({ payload }) => {
      setConcurrentLogin({ at: payload?.at || new Date().toISOString() });
    });
    ch.subscribe();
    sessionChannelRef.current = ch;
  }

  // boot splash removal — snap bar to 100% then fade
  useE(() => {
    const b = document.getElementById("boot");
    if (!b) return;
    const bar = b.querySelector(".boot-bar");
    if (bar) { bar.style.animation = "none"; bar.style.width = "100%"; bar.style.transition = "width 0.15s ease"; }
    setTimeout(() => { b.style.opacity = "0"; setTimeout(() => b.remove(), 400); }, 150);
  }, []);

  // restore Supabase session on page load
  useE(() => {
    sb.auth.getSession().then(async ({ data: { session } }) => {
      if (session) {
        const { data: profile } = await sb.from("profiles")
          .select("role, name, initials, color")
          .eq("id", session.user.id)
          .single();
        if (profile) {
          setSession({ role: profile.role, user: session.user, profile });
          subscribeConcurrentLogin(session.user.id);
        }
      }
    });
    // listen for auth changes
    const { data: { subscription } } = sb.auth.onAuthStateChange(async (event, session) => {
      if (event === "SIGNED_OUT") { setSession(null); setPage("dashboard"); }
      // user clicked invite link — they're signed in but need to set a password
      if (event === "SIGNED_IN" && window.location.hash.includes("type=invite")) {
        setNeedsPassword(true);
        window.location.hash = ""; // clean up URL
      }
    });
    return () => subscription.unsubscribe();
  }, []);

  // theme vars
  useE(() => {
    const root = document.documentElement;
    const a = ACCENTS[t.accent] || ACCENTS.teal;
    Object.entries(a).forEach(([k, v]) => root.style.setProperty(k, v));
    root.style.setProperty("--side-bg", SIDE_TONES[t.sidebarTone] || SIDE_TONES.black);
  }, [t.accent, t.sidebarTone]);

  // simulate load on page / role change
  useE(() => { setLoading(true); const tm = setTimeout(() => setLoading(false), 550); return () => clearTimeout(tm); }, [page, role]);

  // auto-show page tour on first visit — fires when page changes
  useE(() => {
    if (!role) return;
    const tours = buildPageTours(role);
    if (tours[page] && !localStorage.getItem(PAGE_TOUR_KEY(page))) {
      setPageTour(false); // reset first so it re-mounts cleanly
      const tm = setTimeout(() => setPageTour(true), 700);
      return () => clearTimeout(tm);
    } else {
      setPageTour(false);
    }
  }, [page, role]);

  function pushToast(msg, opts = {}) {
    const id = Date.now() + Math.random();
    setToasts(ts => [...ts, { id, msg, ...opts }]);
    setTimeout(() => setToasts(ts => ts.filter(x => x.id !== id)), 2800);
  }

  function login(role, sbUser, profile) {
    const s = { role, user: sbUser, profile };
    setSession(s);
    setPage("dashboard");
    setSelectedId(null);
    subscribeConcurrentLogin(sbUser.id);
  }
  async function logout() {
    if (sessionChannelRef.current) { sb.removeChannel(sessionChannelRef.current); sessionChannelRef.current = null; }
    await sb.auth.signOut();
    setSession(null);
    localStorage.removeItem(SESSION_KEY);
    setConcurrentLogin(null);
    setSelectedId(null);
  }

  // role-based nav items
  const assignedCount = useM(() => { const ids = new Set(TASKS.map(x => x.order)); return EXCEPTIONS.filter(e => ids.has(e.id)).length; }, []);
  const items = role ? ROLE_NAV[role].map(k => {
    const it = { ...NAV_ALL[k] };
    if (k === "assigned") it.badge = assignedCount;
    if (k === "tasks") it.badge = TASKS.filter(x => x.status !== "done").length;
    return it;
  }) : [];

  useE(() => { if (role && !ROLE_NAV[role].includes(page)) setPage("dashboard"); }, [role]);

  function eff(id) {
    const base = EXCEPTIONS.find(e => e.id === id);
    if (!base) return null;
    return {
      ...base,
      status: statusOv[id] || base.status,
      staff: id in staffOv ? staffOv[id] : base.staff,
      priority: prioOv[id] || base.priority,
      timeline: [...base.timeline, ...(wfAdds[id] || [])],
    };
  }
  const selected = selectedId ? eff(selectedId) : null;
  function addWf(id, e) { setWfAdds(m => ({ ...m, [id]: [...(m[id] || []), e] })); }

  function selectRow(row) { setSelectedId(row.id); }
  function resolve(row) { setStatusOv(o => ({ ...o, [row.id]: "resolved" })); addWf(row.id, { e: "Marked resolved", icon: "resolve", t: "just now" }); pushToast(`${row.id} marked resolved`, { icon: "resolve", tone: "ok" }); }
  function escalate(row) { setStatusOv(o => ({ ...o, [row.id]: "escalated" })); addWf(row.id, { e: "Escalated to Warehouse Team", icon: "escalate", t: "just now" }); pushToast(`${row.id} escalated to Warehouse`, { icon: "escalate", tone: "warn" }); }
  function assign(row, staffKey) {
    setStaffOv(o => ({ ...o, [row.id]: staffKey }));
    addWf(row.id, { e: staffKey ? `Assigned to ${STAFF[staffKey].name}` : "Unassigned", icon: "user", t: "just now", actor: staffKey ? undefined : "system" });
    pushToast(staffKey ? `${row.id} assigned to ${STAFF[staffKey].name.split(" ")[0]}` : `${row.id} unassigned`, { icon: "user" });
  }
  function priority(row, lv) { setPrioOv(o => ({ ...o, [row.id]: lv })); addWf(row.id, { e: `Priority changed to ${PRIORITY[lv].label}`, icon: "flag", t: "just now" }); pushToast(`${row.id} priority → ${PRIORITY[lv].label}`, { icon: "flag" }); }
  function replyLogged(row, channel) {
    const ch = channel === "internal" ? "internal escalation note" : channel === "email" ? "email reply" : "WhatsApp reply";
    addWf(row.id, { e: `Customer ${ch} generated by AI`, icon: "ai", t: "just now" });
  }
  function lark(row, action) {
    addWf(row.id, { e: `Sent to Lark ${action.channel}`, icon: "send", t: "just now", actor: "system" });
    pushToast(action.toast, { icon: action.icon === "headset" ? "chat" : action.icon, tone: "ok" });
  }

  function searchSelect(row) {
    const target = ROLE_NAV[role].includes("queue") ? "queue" : ROLE_NAV[role].includes("assigned") ? "assigned" : "dashboard";
    setPage(target);
    setSelectedId(row.id);
  }

  // re-trigger current page tour manually
  function retriggerTour() {
    setPageTour(false);
    setTimeout(() => setPageTour(true), 80);
  }

  // invited user clicked email link — show set-password before entering app
  if (needsPassword) return (
    <>
      <SetPasswordModal onDone={async () => {
        setNeedsPassword(false);
        // re-fetch session + profile after password is set
        const { data: { session } } = await sb.auth.getSession();
        if (session) {
          const { data: profile } = await sb.from("profiles").select("role,name,initials,color").eq("id", session.user.id).single();
          if (profile) { setSession({ role: profile.role, user: session.user, profile }); }
        }
      }} />
      <ToastStack toasts={toasts} />
    </>
  );

  if (!role) return (<><LoginPage onLogin={login} /><ToastStack toasts={toasts} /></>);

  const meta = PAGE_META[page] || { title: NAV_ALL[page]?.label, sub: "" };
  const showPanel = ["dashboard", "queue", "assigned", "orders"].includes(page);
  const currentTours = buildPageTours(role);
  const hasTour = !!currentTours[page];

  return (
    <div className="flex h-screen w-screen overflow-hidden bg-[#fafafa] text-zinc-900" style={{ fontFamily: "var(--font-ui)" }}>
      <Sidebar items={items} active={page} onNav={(k) => { setPage(k); setSelectedId(null); }} collapsed={collapsed} onToggle={() => setCollapsed(c => !c)} user={user} roleKey={role} onLogout={logout} />

      <div className="flex-1 flex flex-col min-w-0">
        <Topbar title={meta.title} subtitle={meta.sub} user={user} roleKey={role} onSearch={() => setSearchOpen(true)} />
        <div className="flex-1 flex min-h-0">
          <main className="flex-1 overflow-y-auto min-w-0">
            {page === "dashboard"    && <DashboardPage loading={loading} selectedId={selectedId} onSelect={selectRow} density={t.density} onNav={(k) => { setPage(k); setSelectedId(null); }} onRefresh={() => { setLoading(true); setTimeout(() => setLoading(false), 600); pushToast("Queue refreshed", { icon: "refresh" }); }} />}
            {page === "queue"        && <QueuePage loading={loading} selectedId={selectedId} onSelect={selectRow} density={t.density} />}
            {page === "assigned"     && <AssignedPage loading={loading} selectedId={selectedId} onSelect={selectRow} density={t.density} />}
            {page === "tasks"        && <MyTasksPage session={session} onToast={pushToast} />}
            {page === "orders"       && <OrdersPage onSelect={selectRow} />}
            {page === "import"       && <ImportPage onToast={pushToast} onAddExceptions={(n) => pushToast(`${n} exceptions added to the queue`, { icon: "plus", tone: "ok" })} />}
            {page === "automations"  && <AutomationsPage canManage={perms.manageAutomations} session={session} onToast={pushToast} onOpenOrder={(r) => { const target = ROLE_NAV[role].includes("queue") ? "queue" : "dashboard"; setPage(target); setSelectedId(r.id); }} />}
            {page === "analytics"    && <AnalyticsPage />}
            {page === "users"        && <UsersPage canManage={perms.manageUsers} onToast={pushToast} />}
            {page === "integrations" && <IntegrationsPage canManage={perms.manageIntegrations} onToast={pushToast} />}
            {page === "deptperms"    && <DeptPermissionsPage onToast={pushToast} />}
            {page === "rules"        && <RulesPage onToast={(m) => pushToast(m)} />}
            {page === "audit"        && <AuditLogPage />}
            {["warranty", "install"].includes(page) && <PlaceholderPage navKey={page} />}
          </main>

          {showPanel && (
            selected
              ? <DetailPanel row={selected} perms={perms} onClose={() => setSelectedId(null)} onResolve={resolve} onEscalate={escalate} onAssign={assign} onPriority={priority} onReplyLogged={replyLogged} onLark={lark} />
              : <DetailEmpty />
          )}
        </div>
      </div>

      <ToastStack toasts={toasts} />

      <AISearchOverlay open={searchOpen} onClose={() => setSearchOpen(false)} onSelect={searchSelect} />

      {/* Per-page contextual tour */}
      {pageTour && hasTour && (
        <PageTour
          key={page}
          page={page}
          role={role}
          onDone={() => setPageTour(false)}
        />
      )}

      {/* ? help button — shows when tour is not active and current page has a tour */}
      {hasTour && !pageTour && (
        <button
          onClick={retriggerTour}
          title={`Guide: ${PAGE_META[page]?.title || page}`}
          style={{
            position: "fixed", bottom: 24, right: 24, zIndex: 50,
            width: 36, height: 36, borderRadius: "50%",
            background: "var(--accent)", color: "#fff",
            border: "2px solid rgba(255,255,255,0.25)", cursor: "pointer",
            display: "flex", alignItems: "center", justifyContent: "center",
            boxShadow: "0 4px 14px rgba(0,0,0,0.18)",
            fontSize: 16, fontWeight: 700, lineHeight: 1,
          }}
        >?</button>
      )}

      {/* Concurrent login warning modal */}
      {concurrentLogin && (
        <div className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-zinc-900/60 backdrop-blur-sm animate-[toastin_0.2s_ease]">
          <div className="bg-white rounded-2xl shadow-2xl border border-zinc-200 w-full max-w-[400px] overflow-hidden">
            <div className="flex items-start gap-3 px-5 pt-5 pb-4 border-b border-zinc-100">
              <div className="flex items-center justify-center w-10 h-10 rounded-xl shrink-0" style={{ background: "#fef2f2", color: "#dc2626" }}>
                <Icon name="alert" size={20} />
              </div>
              <div>
                <h3 className="text-[15px] font-semibold text-zinc-900">New sign-in detected</h3>
                <p className="text-[12.5px] text-zinc-500 mt-0.5">
                  Your account was just signed in from another device or browser
                  {concurrentLogin.at ? ` at ${new Date(concurrentLogin.at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}` : ""}.
                </p>
              </div>
            </div>
            <div className="px-5 py-4 bg-amber-50/60">
              <p className="text-[13px] text-zinc-700 leading-relaxed">
                If <strong>this was you</strong> signing in on another tab or device, you can dismiss this.
                If <strong>this wasn't you</strong>, sign out immediately and contact your admin.
              </p>
            </div>
            <div className="flex items-center gap-2 px-5 py-3.5 border-t border-zinc-100 bg-zinc-50/50">
              <button onClick={logout}
                className="flex-1 h-9 rounded-lg border border-red-200 text-red-600 text-[13px] font-semibold hover:bg-red-50 transition-colors">
                Sign out now
              </button>
              <button onClick={() => setConcurrentLogin(null)}
                className="flex-1 h-9 rounded-lg text-white text-[13px] font-semibold transition-all hover:brightness-110"
                style={{ background: "var(--accent)" }}>
                Dismiss — that was me
              </button>
            </div>
          </div>
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Theme" />
        <TweakColor label="Accent" value={t.accent === "teal" ? "#0d9488" : t.accent === "indigo" ? "#4f46e5" : t.accent === "blue" ? "#2563eb" : "#e11d48"}
          options={["#0d9488", "#4f46e5", "#2563eb", "#e11d48"]}
          onChange={(hex) => setTweak("accent", { "#0d9488": "teal", "#4f46e5": "indigo", "#2563eb": "blue", "#e11d48": "rose" }[hex])} />
        <TweakRadio label="Sidebar" value={t.sidebarTone} options={["black", "slate", "stone"]} onChange={v => setTweak("sidebarTone", v)} />
        <TweakSection label="Table" />
        <TweakRadio label="Density" value={t.density} options={["compact", "comfortable"]} onChange={v => setTweak("density", v)} />
      </TweaksPanel>
    </div>
  );
}

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