/* guide.jsx — the in-app tutorial ("Guide" tab).

   ⚠ KEEP THIS IN SYNC WITH THE PRODUCT. Whenever a feature is added, changed or
   removed anywhere in Sinux Signals, update the matching section here in the
   same change. The "snapshots" are live mock components rendered with the real
   design system, so they restyle themselves — but the words must be maintained.

   Role gating: every section declares the minimum role that can see it.
   Viewers get the core guide; analysts also see their own-scope tools; editors
   the editor tools; admins (and the owner) see everything below their rank too.
   A couple of sections are gated by a capability rather than rank (e.g. Bug
   triage needs the owner-granted bugs cap) — those take an extra prop.
*/

const GUIDE_RANK = { viewer: 1, analyst: 2, editor: 3, admin: 4, server_owner: 5, owner: 6 };

const GUIDE_ROLE_LABEL = {
  viewer: "Everyone",
  analyst: "Analyst+",
  editor: "Editor+",
  admin: "Admin+",
  server_owner: "Server owner",
  owner: "Owner",
};

function GuideRoleChip({ min }) {
  return <span className={cx("guide-role", "r-" + min)}>{GUIDE_ROLE_LABEL[min]}</span>;
}

// "Snapshot" frame — a faux window around a live mock of the real UI, so the
// examples always match the current design instead of going stale like PNGs.
function Shot({ caption, children }) {
  return (
    <figure className="guide-shot">
      <div className="guide-shot-bar"><span /><span /><span /></div>
      <div className="guide-shot-body">{children}</div>
      {caption && <figcaption className="guide-shot-cap">{caption}</figcaption>}
    </figure>
  );
}

function GSection({ id, title, min, role, children }) {
  if ((GUIDE_RANK[role] || 0) < (GUIDE_RANK[min] || 99)) return null;
  return (
    <section id={"g-" + id} className="guide-sec">
      <h3 className="guide-sec-title">{title} <GuideRoleChip min={min} /></h3>
      {children}
    </section>
  );
}

// ── Live demo snippets ───────────────────────────────────────────────────────

const GUIDE_ACTION_DESC = {
  open:   "A new position was entered.",
  add:    "Bought more of an existing position.",
  trim:   "Sold part — banking profit, keeping runners.",
  cut:    "Stopped out / exited because the setup failed.",
  close:  "Full exit — the position moves to Recaps.",
  hold:   "No buy or sell — a status update on an open position.",
  watch:  "An idea being watched. Not a position (lives in Watchlist).",
  expire: "An option reached expiry with no exit posted.",
};

function DemoActions() {
  return (
    <div className="guide-rows">
      {Object.keys(GUIDE_ACTION_DESC).map(a => (
        <div key={a} className="guide-row">
          <ActionPill kind={a} />
          <span className="guide-row-txt">{GUIDE_ACTION_DESC[a]}</span>
        </div>
      ))}
    </div>
  );
}

function DemoInstruments() {
  return (
    <div className="guide-rows">
      <div className="guide-row"><InstrumentTag event={{ instrument: "option", optionType: "call" }} /><span className="guide-row-txt">Call option — profits when the underlying rises.</span></div>
      <div className="guide-row"><InstrumentTag event={{ instrument: "option", optionType: "put" }} /><span className="guide-row-txt">Put option — profits when the underlying falls.</span></div>
      <div className="guide-row"><InstrumentTag event={{ instrument: "shares" }} /><span className="guide-row-txt">Plain shares — no strike or expiry.</span></div>
    </div>
  );
}

function DemoConfidence() {
  return (
    <div className="guide-rows">
      <div className="guide-row"><ConfPill level="high" /><span className="guide-row-txt">An explicit call with a clear action ("Bought NVDA 210C 6/20 @ 4.40").</span></div>
      <div className="guide-row"><ConfPill level="medium" /><span className="guide-row-txt">Probably a trade, but loosely worded.</span></div>
      <div className="guide-row"><ConfPill level="low" /><span className="guide-row-txt">Ambiguous chatter — read the raw message before acting on it.</span></div>
    </div>
  );
}

function DemoPositionRow() {
  return (
    <div className="guide-posrow">
      <div className="guide-posrow-l">
        <span className="ticker">$NVDA</span>
        <InstrumentTag event={{ instrument: "option", optionType: "call" }} />
        <span className="mono guide-dim">Jun 20 · 210 · SL 3.80</span>
      </div>
      <div className="guide-posrow-r">
        <span className="mono">4.40 <span className="guide-up">→ 6.10 (+38.6%)</span></span>
        <ActionPill kind="hold" />
        <ConfPill level="high" />
      </div>
    </div>
  );
}

function DemoDiscordFlow() {
  return (
    <div className="guide-flow">
      <div className="guide-discord">
        <span className="guide-discord-author">DEV</span>
        <span className="guide-discord-msg">Bought $NVDA 210C 6/20 @ 4.40 — SL 3.80</span>
      </div>
      <div className="guide-flow-arrow mono">→ parsed →</div>
      <div className="guide-parsed mono">
        <span>ticker <strong>NVDA</strong></span>
        <span>action <strong>open</strong></span>
        <span>contract <strong>6/20 210C</strong></span>
        <span>fill <strong>4.40</strong></span>
        <span>stop <strong>3.80</strong></span>
      </div>
    </div>
  );
}

// ── The tab ──────────────────────────────────────────────────────────────────

function GuideTab({ role, canManage, canViewBugs, canViewRatings }) {
  const r = role || "viewer";
  const toc = [
    ["what", "What is Sinux Signals", "viewer"],
    ["reading", "Reading a signal", "viewer"],
    ["positions", "Open Positions", "viewer"],
    ["events", "Signal Events", "viewer"],
    ["watchlist", "Watchlist", "viewer"],
    ...(window.CHARTS_ENABLED ? [["charts", "Charts", "viewer"]] : []),
    ["recaps", "Recaps", "viewer"],
    ["sources", "Sources", "viewer"],
    ["notifications", "Notifications", "viewer"],
    ["install", "Install the app", "viewer"],
    ["tips", "Tips & shortcuts", "viewer"],
    ["report", "Reporting a bug", "viewer"],
    ["rating", "Rating the platform", "viewer"],
    ["an-tools", "Your tools", "analyst"],
    ["ed-access", "Managing users", "editor"],
    ["ed-audit", "Audit log", "editor"],
    ["ad-channels", "Channels", "admin"],
    ["ad-analysts", "Analysts", "admin"],
    ["ad-manage", "Managing positions", "admin"],
    ["ad-cleanup", "Correcting data", "admin"],
    ["ad-access", "Access & roles", "admin"],
    ...(canViewBugs ? [["ad-bugs", "Bug triage", "admin"]] : []),
    ...(canViewRatings ? [["ad-ratings", "Ratings", "admin"]] : []),
    ["ow-servers", "Servers & integrations", "owner"],
  ].filter(([, , min]) => (GUIDE_RANK[r] || 0) >= (GUIDE_RANK[min] || 99));

  function jump(id) {
    const el = document.getElementById("g-" + id);
    if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
  }

  return (
    <div className="guide">
      <aside className="guide-toc">
        <div className="guide-toc-hdr mono">On this page</div>
        {toc.map(([id, label, min]) => (
          <button key={id} className="guide-toc-item" onClick={() => jump(id)}>
            <span>{label}</span>
            {min !== "viewer" && <GuideRoleChip min={min} />}
          </button>
        ))}
        <div className="guide-toc-foot mono">Guide for your role: <strong>{r}</strong> · v{APP_VERSION}</div>
      </aside>

      <div className="guide-body">
        <div className="guide-tour-cta">
          <div className="gtc-txt">
            <div className="gtc-title">{I("zap", { size: 15 })} New here? Take the guided tour</div>
            <div className="gtc-sub">A one-minute interactive walkthrough of the app — it points out each part as you go. Skip or replay it anytime.</div>
          </div>
          <button className="btn primary" onClick={() => window.dispatchEvent(new CustomEvent("sinux:start-tour"))}>Start tour</button>
        </div>

        <GSection id="what" title="What is Sinux Signals?" min="viewer" role={r}>
          <p>Sinux Signals turns analysts' Discord posts into a live trading dashboard. A bot watches the registered channels; every message from a registered analyst is parsed by AI into a structured trade event, which flows into <strong>Positions</strong>, <strong>Events</strong>, <strong>Watchlist</strong>{window.CHARTS_ENABLED && <>, <strong>Charts</strong></>} and <strong>Recaps</strong> within seconds — no one types anything in by hand.</p>
          <Shot caption="A Discord message becomes structured data automatically.">
            <DemoDiscordFlow />
          </Shot>
          <p className="guide-note">Everything you see mirrors what an analyst posted — it is information, not financial advice. When in doubt, every row lets you read the original message.</p>
        </GSection>

        <GSection id="reading" title="Reading a signal" min="viewer" role={r}>
          <p>Every event carries an <strong>action</strong>, an <strong>instrument</strong> and a <strong>confidence</strong>:</p>
          <Shot caption="Actions — what the analyst did.">
            <DemoActions />
          </Shot>
          <Shot caption="Instruments — what they're trading.">
            <DemoInstruments />
          </Shot>
          <Shot caption="Confidence — how explicit the message was.">
            <DemoConfidence />
          </Shot>
          <p>Option contracts read like <span className="mono">6/20 210C</span>: expiry June 20, strike 210, Call. A price like <span className="mono">4.40 → 6.10</span> means the fill was 4.40 and the latest posted mark is 6.10.</p>
        </GSection>

        <GSection id="positions" title="Open Positions" min="viewer" role={r}>
          <p>The home tab. One row per live position, aggregated from every related event (open → adds → trims → …).</p>
          <Shot caption="A position: contract details on the left, entry → latest mark with P&L on the right.">
            <DemoPositionRow />
          </Shot>
          <ul className="guide-list">
            <li><strong>Entry → Now</strong> uses the analyst's stated average when given, else the first fill. The arrow value is the latest price evidence — a posted mark or the most recent fill (a trim @ 10 moves "now" to 10) — with the % move.</li>
            <li><strong>Stop</strong> shows the latest stop-loss the analyst stated (— when none).</li>
            <li><strong>Click a row</strong> to expand the full interaction timeline — every event on that trade, in order, with the entry anchored at the left and the follow-ups (adds / trims / holds / exits) stepped in beneath it.</li>
            <li>The <strong>calculator</strong> button converts contracts ↔ cost or budget ↔ contracts{window.CHARTS_ENABLED && <>; the <strong>chart</strong> button jumps to the ticker's chart</>}.</li>
            <li>Options <strong>auto-expire</strong>: a 0DTE with no update by the end of its session — or any dated contract past its expiry — moves to Recaps on its own.</li>
            <li>Filter with the <strong>ticker</strong> search box, the <strong>analyst</strong> dropdown (pick one or more — it lists only analysts who currently have open positions), the instrument toggle (All / Options / Shares), and the timeframe (<span className="mono">This week</span> / 2W / 1M / All — defaults to the current trading week).</li>
            <li>On mobile the table becomes a tap-to-expand card list; the same details live inside each card.</li>
          </ul>
        </GSection>

        <GSection id="events" title="Signal Events" min="viewer" role={r}>
          <p>The raw feed — every parsed event, newest first, before any aggregation. Use it to see exactly what came in and when.</p>
          <ul className="guide-list">
            <li>Filter by ticker (search box), analyst (a dropdown of who's in the feed), category (Signals / Analysis / Chat), action, or instrument.</li>
            <li>The <strong>Price</strong> column shows the fill stated in that message, plus <span className="mono">→ mark</span> when the message also gave a current value.</li>
            <li><strong>Click a row</strong> to read the full raw Discord message.</li>
            <li>Parsed wrong? Hit <strong>Override</strong> to correct it (ticker, action, instrument, prices, stop…). The owner can fix any event; an analyst can fix their own — and every view updates from the correction.</li>
          </ul>
        </GSection>

        <GSection id="watchlist" title="Watchlist" min="viewer" role={r}>
          <p>Ideas, not positions. When an analyst floats tickers — a daily watchlist, levels to watch, an analysis write-up — it lands here, grouped by day. Nothing in Watchlist affects Positions or Recaps until the analyst actually takes a trade.</p>
          <p>A <strong>timeframe</strong> control (top right, same as Positions) keeps the feed current — <span className="mono">This week / 2W / 1M / All</span>, defaulting to the <strong>last 2 weeks</strong> so stale ideas fall away on their own. There's also an <strong>analyst</strong> dropdown and a type toggle (All / Watchlists / Analysis); the search box narrows by ticker.</p>
        </GSection>

        {window.CHARTS_ENABLED && (
        <GSection id="charts" title="Charts" min="viewer" role={r}>
          <p>Candles for any ticker with the analysts' events marked in price context. Switch the timeframe with the buttons above the chart — <span className="mono">5m / 15m / 1H</span> intraday through <span className="mono">D / W / M</span> — markers and axis adapt automatically (intraday only shows events from its own window). The panels beside the chart list the ticker's open positions and recent events — same format as the main tabs.</p>
        </GSection>
        )}

        <GSection id="recaps" title="Recaps" min="viewer" role={r}>
          <p>Closed history. The top of the tab summarizes the selected analysts' track record — <strong>win rate</strong>, wins/losses, average P&amp;L (and average win vs average loss) — plus a <strong>wins &amp; losses chart</strong>: one bar per closed trade, green up for wins, red down for losses, oldest to newest (hover any bar for the ticker and result).</p>
          <p>Below that, switch between two views. <strong>Periods</strong> rolls closed trades up into recaps — pick <span className="mono">Daily / Weekly / Monthly / 3M / 6M / 12M</span> and each bucket shows that period's win rate, total &amp; average gains, and the trades grouped by analyst (click a line to expand the full timeline). <strong>Trades</strong> lists every closed position as its own card with the full timeline, sortable by Recent / Top wins / Top losses. Both are derived from our own tracked data, so every server gets recaps with no extra setup. An <strong>analyst</strong> dropdown narrows to specific analysts and the search box filters by ticker.</p>
          <p>If a server runs a <strong>Recap channel</strong> (a daily published summary of closed trades), the Daily view also overlays the analyst's <strong>published recap</strong> for that day — its headline numbers plus a badge showing whether it <em>reconciles</em> with our data or has flags. The owner can toggle this per server (Settings → Servers) and works any reconciliation discrepancies from a panel at the top of the tab.</p>
        </GSection>

        <GSection id="sources" title="Sources" min="viewer" role={r}>
          <p>Choose whose signals you see. Toggle an analyst off and they disappear from every tab; <strong>solo</strong> isolates one analyst. The server picker (top right) does the same per Discord server when more than one is connected.</p>
        </GSection>

        <GSection id="notifications" title="Notifications" min="viewer" role={r}>
          <p>The <strong>{I("bell", { size: 12 })} bell</strong> at the top right keeps you on top of new signals — a badge shows how many you haven't seen, and the dropdown lists them newest-first. Tap any one to jump to that ticker. The in-app bell always works on its own; <strong>push notifications</strong> are the optional extra that reach you <strong>even when the app is closed</strong> (delivered by a service worker, not just while the tab is open).</p>
          <p><strong>Turn on push notifications — step by step:</strong></p>
          <ol className="guide-list guide-steps">
            <li><strong>On iPhone / iPad, install the app first.</strong> Apple only delivers push to a web app that's been <strong>added to the Home Screen</strong> (see <em>Install the app</em> below), and you must open it <em>from that icon</em>. On Android and desktop you can skip straight to step 2.</li>
            <li>Tap the <strong>{I("bell", { size: 12 })} bell</strong> (top right), then the <strong>settings gear</strong> in the dropdown.</li>
            <li>Switch <strong>Push notifications on</strong>, and tap <strong>Allow</strong> when your browser or device asks for permission (it only asks once, per device).</li>
            <li><strong>Choose what you hear about</strong> — tick Entries, Adds, Trims, Cuts, Closes, Updates, plus <strong>Watchlist</strong> (new watchlist / analysis ideas) and <strong>Recaps</strong> (a new daily recap published). By default you'll get <strong>entries and exits</strong> (open / trim / cut / close); holds, watchlist and recaps are off unless you switch them on.</li>
          </ol>
          <ul className="guide-list">
            <li>Alerts follow the analysts you've <strong>enabled in Sources</strong> and the servers you're scoped to — turn an analyst off and they go quiet.</li>
            <li>Preferences are saved <strong>per device</strong>, so you can run loud alerts on your phone and stay silent on your laptop.</li>
            <li>Not getting push? Re-open the bell's settings to confirm it's on, check you didn't block notifications for the site in your browser/OS, and on iPhone make sure you opened the app from its <strong>home-screen icon</strong> (not a Safari tab).</li>
          </ul>
        </GSection>

        <GSection id="install" title="Install the app (Add to Home Screen)" min="viewer" role={r}>
          <p>Sinux Signals is a web app you can <strong>install like a native one</strong> — its own icon, full-screen with no browser bars, and (on iPhone/iPad) the only way to receive push alerts. It takes a few seconds:</p>
          <ul className="guide-list">
            <li><strong>iPhone / iPad — Safari</strong>: tap the <strong>Share</strong> button (the square with an ↑ arrow) → scroll down to <strong>Add to Home Screen</strong> → <em>Add</em>. Then open Sinux Signals from the new icon. It <em>must</em> be Safari — Chrome and other iOS browsers can't add it.</li>
            <li><strong>Android — Chrome</strong>: tap the <strong>⋮</strong> menu (top right) → <strong>Add to Home screen</strong> (or <em>Install app</em>) → <em>Install</em>. Many phones also pop up a one-tap <em>Install</em> banner you can just accept.</li>
            <li><strong>Android — Samsung Internet / Firefox</strong>: tap the menu (<strong>⋮</strong> or ≡) → <strong>Add page to → Home screen</strong> / <strong>Install</strong>.</li>
            <li><strong>Desktop — Chrome / Edge</strong>: click the <strong>install icon</strong> in the address bar (a small monitor with a ↓, at the right end) — or the <strong>⋮</strong> menu → <em>Install Sinux Signals…</em>. It opens in its own window.</li>
            <li><strong>Desktop — Safari (macOS)</strong>: <strong>File → Add to Dock</strong>.</li>
          </ul>
          <p className="guide-note">If you only have access to a single <strong>branded</strong> community, the installed icon and name match that community (e.g. Charan Invests) rather than Sinux Signals — iOS captures whatever's on screen when you add it, so sign in first, and <em>re-add</em> the app later to refresh the icon.</p>
        </GSection>

        <GSection id="tips" title="Tips & shortcuts" min="viewer" role={r}>
          <ul className="guide-list">
            <li><strong>Theme</strong>: sun / moon / monitor switch at the bottom of the sidebar (system follows your OS).</li>
            <li><strong>Density</strong>: the Tweaks panel offers Tight / Std / Roomy table density.</li>
            <li><strong>Phone</strong>: install it to your home screen for a full-screen app with its own icon — see <strong>Install the app</strong> above for the steps on every device.</li>
            <li>The thin green line at the very top on mobile is a reading-progress bar — it fills as you scroll.</li>
            <li>The search box clears when you switch tabs, so a filter never follows you to the wrong page.</li>
          </ul>
        </GSection>

        <GSection id="report" title="Reporting a bug" min="viewer" role={r}>
          <p>Spotted something broken, odd, or missing? Use the <strong>{I("bug", { size: 12 })} Report a bug</strong> button at the bottom of the sidebar — it's there on every page, for every user.</p>
          <ul className="guide-list">
            <li>Pick <strong>Bug</strong> for something broken, <strong>Idea</strong> for a feature suggestion, or <strong>Other</strong> for anything else.</li>
            <li>Describe <strong>what happened and what you expected</strong> — the page you were on, the ticker or analyst involved, and what you tapped right before. The more specific, the faster the fix.</li>
            <li>Your role, app version and current page are attached automatically, so you don't need to include technical details.</li>
            <li>Reports go straight to the Sinux Signals team and are tracked until resolved. Thank you — every report makes the platform better for your whole community.</li>
          </ul>
        </GSection>

        <GSection id="rating" title="Rating the platform" min="viewer" role={r}>
          <p>Every so often a small card slides in from the corner asking you to <strong>rate your Sinux Signals experience</strong> — tap 1–5 stars, add a note if you like, and hit Submit. It's entirely optional and never blocks the app; choose <em>Maybe later</em> to dismiss it, and it won't return until the next cycle.</p>
          <ul className="guide-list">
            <li>How often you're asked — and whether you're asked at all — is set by your community's owner. By default it's at most <strong>once a day</strong>.</li>
            <li>Only your <strong>star score and optional note</strong> are sent (along with your name and current server, so the owner knows whose feedback it is). Nothing else is collected.</li>
          </ul>
        </GSection>

        <GSection id="an-tools" title="Your tools (Analyst)" min="analyst" role={r}>
          <p>As an analyst you have read-only access to every tab, plus a few actions scoped to <strong>your own</strong> signals:</p>
          <ul className="guide-list">
            <li><strong>Override your own events</strong> — hit <strong>Override</strong> on any of your events (an Events row, the event flyout, or the pencil on your position's timeline) to fix a mis-parse: ticker, action, instrument, strike/expiry, prices, stop or confidence. Positions, recaps and charts recompute from the correction and it's recorded in the audit log. You can only edit events attributed to you.</li>
            <li><strong>Delete your own events &amp; watchlists</strong> — the trash icon removes your own rows from the Events and Watchlist tabs (every view recomputes instantly). Other analysts' rows are off-limits.</li>
            <li><strong>Manage your own positions</strong> — if the owner has toggled <em>"Can manage positions"</em> on for you (Settings → Access), the green pencil on your position rows lets you log an Add / Trim / Close / Cut or refresh the mark/stop, and <strong>Log position</strong> (top right) opens a new position by hand — all attributed to you and audit-logged. Without that toggle you stay read-only.</li>
          </ul>
          <p className="guide-note">Everything else — channels, other analysts, access and server settings — is admin-only. If you need a correction outside your own trades, ask an admin.</p>
        </GSection>

        <GSection id="ed-access" title="Managing users (Editor)" min="editor" role={r}>
          <p>Editors can grant and manage <strong>viewer</strong> access for their own servers under <em>Settings → Access</em>:</p>
          <ul className="guide-list">
            <li><strong>Add user</strong> with their Discord user ID (right-click a user in Discord → Copy User ID, with Developer Mode on). They then sign in with Discord.</li>
            <li>The <strong>toggle</strong> on each row suspends/reactivates access instantly. Suspension always wins — even if the person holds a granting Discord role.</li>
            <li>You can only manage users whose servers are within your own scope, and only at roles below yours.</li>
          </ul>
          <p>Editors also get <strong>read-only</strong> visibility of the Channels and Analysts screens — you can see how the bot is set up, but the setup how-to (registering channels, analysts) is admin-level and lives in the Admin sections below.</p>
        </GSection>

        <GSection id="ed-audit" title="Audit log (Editor)" min="editor" role={r}>
          <p><em>Settings → Audit log</em> records every configuration change — who did what, to which item, when. Filter by type, server or free text. If something looks different than you remember, check here first.</p>
        </GSection>

        <GSection id="ad-channels" title="Channels (Admin)" min="admin" role={r}>
          <p><em>Settings → Channels</em> controls what the bot reads. Register a channel with its Discord ID — the <strong>name fills in automatically</strong> from the ID (you can still edit it) — and tag it with one or more categories — a channel can be both <strong>Signals</strong> and <strong>Analysis</strong> at once (toggle the pills). A newly registered channel <strong>defaults to Signals + Analysis</strong> (both alerts and write-ups) — narrow it to either if you like. <strong>Recap</strong> and <strong>Ignore</strong> own the whole channel, so they can't be combined with others:</p>
          <ul className="guide-list">
            <li><strong>Signals</strong> — highest priority; every message from a registered analyst is parsed.</li>
            <li><strong>Analysis</strong> — parsed with context; watchlists and write-ups usually live here.</li>
            <li><strong>Recap</strong> — a daily published summary of closed/TP'd trades. The bot reads each line, attributes it to its analyst, and <strong>reconciles it read-only</strong> against tracked positions: a play still open per the analyst is <em>confirmed</em>, and genuine discrepancies (a trade we never tracked, an unknown analyst, or an exit at a materially different price) are <em>flagged</em> for the owner. It <strong>never closes a position from a recap</strong> — the analyst's own words are the source of truth; fix a true mis-parse with <strong>Override</strong>, not the recap. Lock the channel to the recap sender via its <strong>Authors</strong>.</li>
            <li><strong>Chat</strong> — noisy; only registered analysts are parsed.</li>
            <li><strong>Ignore</strong> — skipped entirely (bots, screeners, off-topic).</li>
          </ul>
          <p>Per channel you can also force an <strong>instrument mode</strong> (options-only / shares-only) and bind one or more <strong>Authors</strong> — only those accounts are parsed there (leave it empty to allow any registered analyst). That's how you lock a recap channel to its sender, or let a trader who posts both himself and via his bot flow into one channel. With multiple servers connected, the list groups by server.</p>
        </GSection>

        <GSection id="ad-analysts" title="Analysts (Admin)" min="admin" role={r}>
          <p><em>Settings → Analysts</em> is the registry of who gets parsed. Only registered analysts produce events.</p>
          <ul className="guide-list">
            <li>Register by Discord user ID — the handle <strong>auto-fills</strong> from the user's Discord name (edit it for a custom label) — and each analyst gets a color used across the app.</li>
            <li><strong>Tier</strong> (core / secondary / watchlist) weights how aggressively their chat-channel posts are parsed.</li>
            <li><strong>Channel scope</strong> limits which categories they're parsed in (signals / analysis / chat).</li>
            <li><strong>Bot/app analyst</strong>: turn this on for app-posted alerts (embeds and post-then-edit messages are captured).</li>
            <li><strong>Breakeven on trim</strong>: for analysts whose convention is "trim = runners with stop at entry", a trim with no stated stop auto-sets the stop to the entry price.</li>
            <li><strong>Read images</strong> (off by default): for analysts who post their calls as pictures, the bot vision-reads them. It's <em>conservative</em> — a posted <strong>portfolio / closed-trades table</strong> is reconciled read-only against what we track (gaps show as reconciliation flags, nothing is auto-closed), and a <strong>single-trade card</strong> updates the mark on a position we already track or is flagged for you to confirm. An image never opens or closes a position on its own.</li>
          </ul>
        </GSection>

        <GSection id="ad-manage" title="Managing positions from the portal (Admin)" min="admin" role={r}>
          {canManage ? (
            <p>You have position management enabled. Two tools, both on the Positions tab:</p>
          ) : (
            <p className="guide-note">This capability is granted per admin by the owner (Settings → Access → edit an admin → "Can manage positions"). You'll see the tools below once it's enabled for you.</p>
          )}
          <ul className="guide-list">
            <li><strong>Manage</strong> (the green pencil on a position row): log an Add / Trim / Close / Cut, or just refresh the mark/stop — for when an analyst's update didn't get posted or parsed. On a trim / cut / close you can enter the fill as a <strong>$ price or a % gain</strong> — flip the field between the two and a % auto-computes the dollar fill from the position's entry (entry 5.18 + 16% → $6.01), so P&amp;L reads correctly (it falls back to $ when there's no recorded entry).</li>
            <li><strong>Log position</strong> (top right): open a brand-new position by hand — pick the analyst, ticker, option/shares, strike & expiry, entry price.</li>
            <li>Both write a real event attributed to the analyst, tagged with a <span className="manual-tag">portal</span> chip in the timeline, and recorded in the audit log.</li>
          </ul>
        </GSection>

        <GSection id="ad-cleanup" title="Correcting data (Admin)" min="admin" role={r}>
          <ul className="guide-list">
            <li><strong>Override a mis-parsed event</strong> — the <strong>Override</strong> action (on an Events row, the event flyout, or the pencil on a position's timeline) opens an editor pre-filled with the parse. Fix the ticker, action, instrument, strike/expiry, prices, stop or confidence and save — positions, recaps and charts recompute, and the change is in the audit log. The <strong>owner</strong> can override any event; an <strong>analyst</strong> can override their own.</li>
            <li><strong>Delete an event</strong> from the Events tab (trash icon, or multi-select for bulk). Positions, recaps and charts recompute instantly — deleting a bad parse fixes every view at once.</li>
            <li><strong>Watchlist entries</strong> delete the same way from the Watchlist tab.</li>
            <li>A wrong price on a live position? Use <strong>Manage → Update</strong> to post the correct mark rather than deleting history.</li>
          </ul>
        </GSection>

        <GSection id="ad-access" title="Access & roles (Admin)" min="admin" role={r}>
          <p>Two ways into the portal, both under <em>Settings → Access</em>:</p>
          <ul className="guide-list">
            <li><strong>Individual users</strong> — added by Discord ID with a role:
              <ul className="guide-list" style={{ marginTop: 6 }}>
                <li><strong>Viewer</strong> — read-only.</li>
                <li><strong>Analyst</strong> — read-only, plus they can delete <em>their own</em> events & watchlists and (if you toggle it) manage <em>their own</em> positions.</li>
                <li><strong>Editor</strong> — manages individual users (viewers), sees the audit log and read-only config.</li>
                <li><strong>Admin</strong> — pick exactly what they can do with per-user toggles: manage channels, manage analysts, manage users & access, manage & moderate positions, manage server settings, and (owner-granted only) view bug reports.</li>
                <li><strong>Server owner</strong> — a full clone of admin for their servers: channels, analysts, users, positions and server settings, all on.</li>
              </ul>
            </li>
            <li><strong>Role access</strong> — map a Discord role to an access level (any of the roles above) and everyone holding it can sign in. Checks are live: granting or removing the Discord role takes effect within about a minute, no re-login needed.</li>
            <li>An individual entry always <strong>overrides</strong> role grants — add someone as Active to let them in regardless of roles, or Suspend them to keep them out even if they hold a granting role.</li>
            <li>Everyone except the owner is <strong>scoped to their own servers</strong>; only the owner sees everything and can add/remove servers and view all bug reports.</li>
          </ul>
        </GSection>

        {canViewBugs && (
        <GSection id="ad-bugs" title="Bug triage (Admin)" min="admin" role={r}>
          <p>The owner has granted you the <strong>Bugs</strong> tab — every report filed through "Report a bug" across your scope, so you can triage them.</p>
          <ul className="guide-list">
            <li>Each report shows the <strong>reporter</strong>, their <strong>type</strong> (Bug / Idea / Other), the message, and the <strong>context</strong> captured automatically — app version, the page they were on, and viewport.</li>
            <li>Move a report through <strong>open → resolved / dismissed</strong> as you work it. The sidebar <strong>Bugs</strong> badge counts what's still open.</li>
            <li>Filter by status or type to focus a triage session; resolved and dismissed reports stay on record.</li>
          </ul>
          <p className="guide-note">This tab is visible only to users the owner has granted the bug-reports capability (Settings → Access → edit an admin → "View bug reports"). Reporting a bug, by contrast, is open to everyone.</p>
        </GSection>
        )}

        {canViewRatings && (
        <GSection id="ad-ratings" title="Ratings (Admin)" min="admin" role={r}>
          <p>The <strong>Ratings</strong> tab aggregates the platform-satisfaction feedback users submit through the rating prompt{r === "owner" ? " across every server" : ", scoped to your own servers"}.</p>
          <ul className="guide-list">
            <li><strong>At a glance</strong>: the average score with a star meter, the full 1–5 <strong>distribution</strong>, and a <strong>daily-average</strong> trend strip. Switch the timeframe (30d / 90d / 180d / 1Y){r === "owner" && <>; with more than one server you can also filter by server</>}.</li>
            <li><strong>Individual ratings</strong> lists every submission with its stars, the user, the server and any note — filter to 4–5★ / 3★ / 1–2★ to focus. Remove any rating with the <strong>trash</strong> icon (it's gone for good).</li>
            {r === "owner"
              ? <li><strong>Prompt settings</strong> (top of the tab, owner only): turn the prompt on or off, set the cadence in hours (default 24 = daily), reword the question, and choose <strong>who's asked</strong> — by <strong>Discord role</strong> (any role you've granted access to under Access → Role access), by <strong>platform access level</strong> (viewers, analysts…), and/or by <strong>specific user</strong>. They combine (a member is asked if they match any), and leaving everything unchecked asks everyone with access. To let a server owner see their servers' ratings, grant it under Settings → Access → their user → <em>"View ratings"</em>.</li>
              : <li>You're seeing this because the owner granted you the <em>"View ratings"</em> capability. Cadence and targeting are configured by the platform owner.</li>}
          </ul>
        </GSection>
        )}

        <GSection id="ow-servers" title="Servers & integrations (Owner)" min="owner" role={r}>
          <ul className="guide-list">
            <li><strong>Servers</strong>: connect a Discord server by guild ID (the bot must already be a member) — the display name <strong>auto-fills</strong> from the server. Deleting a server removes all of its channels, analysts, events, watchlist entries and role grants.</li>
            <li><strong>White-label branding</strong>: a server can carry its own portal identity (name, mark, colors). Members who only see that server get its branded portal — with a "Powered by Sinux Signals" sub-line — and you can preview it by selecting just that server in the server picker. Configured per server (branding specs are applied by the developer).</li>
            <li><strong>Integrations</strong>: the live endpoint configuration the dashboard polls. If the feed shows "paused" or "endpoint error", start here.</li>
            <li><strong>Per-admin position management</strong>: grant or revoke the "Can manage positions" switch on each admin in Access.</li>
            <li><strong>Usage</strong> (owner-only tab): how the platform is consumed — daily active users, activity volume (~5-minute blocks), signals ingested per day / analyst / server, config-change volume, and a per-user activity table. A <strong>server filter</strong> scopes the signal &amp; configuration metrics to one server (the "by server" panel becomes a per-channel breakdown); user activity is tracked platform-wide, so those panels stay platform-wide and are labelled as such.</li>
            <li><strong>Bugs</strong>: you can grant the Bugs tab to any admin (Access → "View bug reports"); triaging it is covered in <em>Bug triage</em> above. As owner you always see every report across all servers.</li>
            <li>Bot-side changes (parser behavior) live in the bot project and need a bot restart to apply.</li>
          </ul>
        </GSection>

        <div className="guide-foot mono">This guide is updated with every release · Sinux Signals v{APP_VERSION}</div>
      </div>
    </div>
  );
}

Object.assign(window, { GuideTab });
