/* ratings.jsx — the daily "rate the platform" prompt + the owner/server-owner
   Ratings tab (aggregate visualization, per-rating list with delete, and the
   owner-only campaign config: cadence + who is asked).

   The prompt is a gentle bottom-corner card, never a blocking modal. Who gets
   asked and how often is set by the owner (GET/PATCH /api/rating-config); the
   backend also de-dupes to one rating per cadence window. Results are scoped:
   the owner sees everything; a server owner granted the ratings cap sees only
   their own servers' ratings.
*/

const RATING_ROLE_LABEL = {
  viewer: "Viewers", analyst: "Analysts", editor: "Editors",
  admin: "Admins", server_owner: "Server owners", owner: "Owner",
};

// A precise, partially-fillable star meter (e.g. 4.3 → 4.3 gold stars).
function StarMeter({ value, size = 16, max = 5 }) {
  const pct = Math.max(0, Math.min(100, (Number(value) / max) * 100));
  const row = (fill) => (
    <div className="sm-row" aria-hidden="true">
      {Array.from({ length: max }, (_, i) => (
        <span key={i} className="sm-star">{I("star", { size, fill: fill ? "currentColor" : "none" })}</span>
      ))}
    </div>
  );
  return (
    <div className="star-meter" title={`${Number(value).toFixed(2)} out of ${max}`}>
      <div className="sm-track sm-empty">{row(false)}</div>
      <div className="sm-track sm-full" style={{ width: pct + "%" }}>{row(true)}</div>
    </div>
  );
}

// Interactive star picker used in the prompt.
function StarPicker({ value, onPick, size = 30 }) {
  const [hover, setHover] = useState(0);
  const shown = hover || value;
  return (
    <div className="star-picker" onMouseLeave={() => setHover(0)}>
      {[1, 2, 3, 4, 5].map(n => (
        <button
          key={n} type="button"
          className={cx("star-btn", n <= shown && "on")}
          aria-label={n + " star" + (n > 1 ? "s" : "")}
          aria-pressed={value === n}
          onMouseEnter={() => setHover(n)}
          onFocus={() => setHover(n)}
          onClick={() => onPick(n)}
        >
          {I("star", { size, fill: n <= shown ? "currentColor" : "none" })}
        </button>
      ))}
    </div>
  );
}

// ── The prompt every targeted user sees (bottom-corner card) ─────────────────
const RATING_SNOOZE_KEY = (uid) => "rating:snooze:" + (uid || "anon");

function RatingPrompt({ userId, guildId, tab }) {
  const [state, setState] = useState("hidden"); // hidden | show | done
  const [question, setQuestion] = useState("How is your Sinux Signals experience?");
  const [freqH, setFreqH] = useState(24);
  const [score, setScore] = useState(0);
  const [comment, setComment] = useState("");
  const [busy, setBusy] = useState(false);

  // Per-device snooze so a dismissal isn't re-nagged until the next window.
  const snoozedUntil = () => {
    try { return Number(JSON.parse(localStorage.getItem(RATING_SNOOZE_KEY(userId)) || "null")?.until) || 0; }
    catch (_) { return 0; }
  };
  const snooze = (ms) => { try { localStorage.setItem(RATING_SNOOZE_KEY(userId), JSON.stringify({ until: Date.now() + ms })); } catch (_) {} };

  useEffect(() => {
    let alive = true;
    if (snoozedUntil() > Date.now()) return; // still snoozed on this device
    // Small delay so it doesn't fight the initial load.
    const t = setTimeout(() => {
      tapeFetch(apiBase() + "/api/rating/status")
        .then(r => r.ok ? r.json() : null)
        .then(j => {
          if (!alive || !j || !j.due) return;
          if (j.question) setQuestion(j.question);
          if (j.frequency_hours) setFreqH(j.frequency_hours);
          setState("show");
        })
        .catch(() => {});
    }, 2500);
    return () => { alive = false; clearTimeout(t); };
  }, [userId]);

  async function submit() {
    if (busy || !score) return;
    setBusy(true);
    try {
      await tapeSend(apiBase() + "/api/rating", {
        method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({
          score, comment: comment.trim() || undefined, guild_id: guildId || undefined,
          context: {
            version: typeof APP_VERSION !== "undefined" ? APP_VERSION : "?",
            tab: tab || "?",
            viewport: `${window.innerWidth}×${window.innerHeight}`,
            theme: document.documentElement.getAttribute("data-theme") || "?",
          },
        }),
      });
      // Don't ask again on this device until the next window elapses.
      snooze(Math.max(1, freqH) * 3600e3);
      setState("done");
      setTimeout(() => setState("hidden"), 2600);
    } catch (err) {
      toast.error("Couldn't send — " + err.message);
    } finally {
      setBusy(false);
    }
  }

  function later() {
    snooze(Math.max(1, freqH) * 3600e3);
    setState("hidden");
  }

  if (state === "hidden") return null;

  return (
    <div className={cx("rating-prompt", state === "done" && "is-done")} role="dialog" aria-label="Rate the platform">
      {state === "done" ? (
        <div className="rp-done">
          <span className="rp-done-star">{I("star", { size: 20, fill: "currentColor" })}</span>
          <div>
            <div className="rp-title">Thank you!</div>
            <div className="rp-sub mono">Your feedback helps us improve.</div>
          </div>
        </div>
      ) : (
        <>
          <div className="rp-hdr">
            <div className="rp-title">{question}</div>
            <button className="icon-btn" title="Maybe later" onClick={later}>{I("close", { size: 15 })}</button>
          </div>
          <StarPicker value={score} onPick={setScore} />
          <textarea
            className="rp-note" rows={score ? 3 : 1}
            value={comment} onChange={e => setComment(e.target.value)}
            placeholder={score >= 4 ? "What's working well? (optional)" : score ? "What could be better? (optional)" : "Tap a star to rate"}
          />
          <div className="rp-foot">
            <button className="btn ghost" onClick={later} disabled={busy}>Maybe later</button>
            <button className="btn primary" onClick={submit} disabled={busy || !score}>{busy ? "Sending…" : "Submit"}</button>
          </div>
        </>
      )}
    </div>
  );
}

// ── Aggregation helpers ──────────────────────────────────────────────────────
function summarize(rows) {
  const total = rows.length;
  const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
  let sum = 0, withComment = 0;
  for (const r of rows) { counts[r.score] = (counts[r.score] || 0) + 1; sum += r.score; if (r.comment) withComment++; }
  return { total, counts, avg: total ? sum / total : 0, withComment, promoters: counts[5] + counts[4], detractors: counts[1] + counts[2] };
}
function bandColor(v) {
  if (v >= 4) return "var(--c-open)";
  if (v >= 3) return "var(--c-trim)";
  return "var(--c-down)";
}
// Daily buckets (oldest → newest) for the trend strip.
function dailyBuckets(rows, days) {
  const byDay = new Map();
  for (const r of rows) {
    const d = new Date(r.ts); d.setHours(0, 0, 0, 0);
    const k = d.getTime();
    const b = byDay.get(k) || { k, sum: 0, count: 0 };
    b.sum += r.score; b.count++; byDay.set(k, b);
  }
  const out = [];
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const span = Math.min(days, 45); // cap the strip so bars stay legible
  for (let i = span - 1; i >= 0; i--) {
    const d = new Date(today.getTime() - i * 86400e3);
    const b = byDay.get(d.getTime());
    out.push({ k: d.getTime(), label: d.toLocaleDateString(undefined, { month: "short", day: "numeric" }), avg: b ? b.sum / b.count : 0, count: b ? b.count : 0 });
  }
  return out;
}

// ── Owner-only campaign config ───────────────────────────────────────────────
function RatingConfig({ servers }) {
  const [cfg, setCfg] = useState(null);
  const [users, setUsers] = useState([]);
  const [grants, setGrants] = useState([]);
  const [open, setOpen] = useState(false);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    tapeFetch(apiBase() + "/api/rating-config").then(r => r.ok ? r.json() : null).then(j => { if (j && j.config) setCfg(j.config); }).catch(() => {});
    tapeFetch(apiBase() + "/api/users").then(r => r.ok ? r.json() : null).then(j => { if (j && j.users) setUsers(j.users); }).catch(() => {});
    tapeFetch(apiBase() + "/api/role-grants").then(r => r.ok ? r.json() : null).then(j => { if (j && j.grants) setGrants(j.grants); }).catch(() => {});
  }, []);

  if (!cfg) return null;
  const roles = cfg.target_roles || [];
  const uids = cfg.target_user_ids || [];
  const rgrants = cfg.target_role_grants || [];
  const grantKey = (g) => g.guild_id + ":" + g.role_id;
  const srvName = (gid) => ((servers || []).find(s => s.guild_id === gid)?.name) || "server";
  const multiSrv = (servers || []).length > 1;
  const toggle = (field, val) => setCfg(c => ({ ...c, [field]: (c[field] || []).includes(val) ? c[field].filter(x => x !== val) : [...(c[field] || []), val] }));
  // Only real, enabled, non-blocking grants can be targeted; group them by server.
  const activeGrants = (grants || []).filter(g => g.enabled !== false && (g.level || "viewer") !== "blocked");
  const grantsByGuild = {};
  for (const g of activeGrants) (grantsByGuild[g.guild_id] = grantsByGuild[g.guild_id] || []).push(g);

  async function save() {
    setBusy(true);
    try {
      const j = await tapeSend(apiBase() + "/api/rating-config", {
        method: "PATCH", headers: { "content-type": "application/json" },
        body: JSON.stringify({
          enabled: !!cfg.enabled,
          frequency_hours: Number(cfg.frequency_hours) || 24,
          target_roles: cfg.target_roles || [],
          target_role_grants: cfg.target_role_grants || [],
          target_user_ids: cfg.target_user_ids || [],
          question: cfg.question || "",
        }),
      }).then(r => r.json());
      if (j && j.config) setCfg(j.config);
      toast.success("Rating settings saved");
    } catch (e) { toast.error("Couldn't save — " + e.message); }
    finally { setBusy(false); }
  }

  const anyTarget = roles.length || rgrants.length || uids.length;
  const targetSummary = anyTarget
    ? [
        roles.map(r => RATING_ROLE_LABEL[r] || r).join(", "),
        rgrants.length ? `${rgrants.length} Discord role${rgrants.length > 1 ? "s" : ""}` : "",
        uids.length ? `${uids.length} user${uids.length > 1 ? "s" : ""}` : "",
      ].filter(Boolean).join(" + ")
    : "Everyone with access";

  return (
    <div className="panel rating-config">
      <button className="rc-summary" onClick={() => setOpen(o => !o)}>
        <span className="rc-sum-l">
          {I("sliders", { size: 15 })}
          <span><strong>Prompt settings</strong> <span className="mono rc-dim">· {cfg.enabled ? `every ${cfg.frequency_hours}h` : "off"} · {targetSummary}</span></span>
        </span>
        {I(open ? "chevDown" : "chevRight", { size: 16 })}
      </button>

      {open && (
        <div className="rc-body">
          <label className="rc-toggle">
            <input type="checkbox" checked={!!cfg.enabled} onChange={e => setCfg(c => ({ ...c, enabled: e.target.checked }))} />
            <span><strong>Ask for ratings</strong><span className="rc-hint">When off, no one is prompted.</span></span>
          </label>

          <div className="rc-grid">
            <label className="rc-field">
              <span>How often (hours)</span>
              <input type="number" min="1" max="8760" value={cfg.frequency_hours}
                onChange={e => setCfg(c => ({ ...c, frequency_hours: e.target.value }))} />
              <span className="rc-hint">A user is asked at most once per window (default 24h = daily).</span>
            </label>
            <label className="rc-field">
              <span>Prompt question</span>
              <input type="text" maxLength={200} value={cfg.question || ""}
                onChange={e => setCfg(c => ({ ...c, question: e.target.value }))}
                placeholder="How is your Sinux Signals experience?" />
            </label>
          </div>

          <div className="rc-field">
            <span>Who's asked</span>
            <span className="rc-hint">Leave everything below unchecked to ask <strong>everyone</strong> with access. Otherwise a member is asked if they match <strong>any</strong> of the choices — a Discord role, an access level, or a specific person.</span>
          </div>

          {activeGrants.length > 0 && (
            <div className="rc-field">
              <span>By Discord role (in your servers)</span>
              {Object.keys(grantsByGuild).map(gid => (
                <div key={gid} className="rc-grant-group">
                  {multiSrv && <div className="rc-grant-srv mono">{srvName(gid)}</div>}
                  <div className="rc-chips">
                    {grantsByGuild[gid].map(g => {
                      const k = grantKey(g);
                      return (
                        <button key={k} type="button" className={cx("rc-chip", rgrants.includes(k) && "on")} onClick={() => toggle("target_role_grants", k)}>
                          {g.role_name || g.role_id}
                        </button>
                      );
                    })}
                  </div>
                </div>
              ))}
              <span className="rc-hint">These are the Discord roles you've granted platform access to (Settings → Access → Role access). Anyone holding the role is asked.</span>
            </div>
          )}

          <div className="rc-field">
            <span>By platform access level</span>
            <div className="rc-chips">
              {Object.keys(RATING_ROLE_LABEL).map(r => (
                <button key={r} type="button" className={cx("rc-chip", roles.includes(r) && "on")} onClick={() => toggle("target_roles", r)}>{RATING_ROLE_LABEL[r]}</button>
              ))}
            </div>
          </div>

          {users.length > 0 && (
            <div className="rc-field">
              <span>By specific user</span>
              <div className="rc-chips">
                {users.map(u => (
                  <button key={u.discord_user_id} type="button" className={cx("rc-chip", uids.includes(u.discord_user_id) && "on")} onClick={() => toggle("target_user_ids", u.discord_user_id)}>
                    {u.name || u.discord_user_id}
                  </button>
                ))}
              </div>
            </div>
          )}

          <div className="rc-foot">
            <button className="btn primary" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save settings"}</button>
          </div>
        </div>
      )}
    </div>
  );
}

// ── The Ratings tab ──────────────────────────────────────────────────────────
function RatingsTab({ isOwner, servers, role }) {
  const [days, setDays] = useState(90);
  const [serverId, setServerId] = useState("all");
  const [band, setBand] = useState("all"); // all | positive(4-5) | passive(3) | negative(1-2)
  const [rows, setRows] = useState(null);
  const [err, setErr] = useState(null);
  const avatars = useAvatars((rows || []).map(r => r.user_id));

  const serverName = (gid) => (servers.find(s => s.guild_id === gid)?.name) || (gid ? "—" : "");

  function load() {
    setErr(null);
    let url = apiBase() + "/api/ratings?days=" + days;
    if (isOwner && serverId !== "all") url += "&guild_id=" + encodeURIComponent(serverId);
    tapeFetch(url)
      .then(async r => { const j = await r.json(); if (!r.ok) throw new Error(j.error || "HTTP " + r.status); return j; })
      .then(j => setRows(j.ratings || []))
      .catch(e => setErr(e.message));
  }
  useEffect(() => { setRows(null); load(); }, [days, serverId]);

  async function remove(row) {
    if (!(await confirmDialog({ title: "Delete rating", message: "Delete this rating permanently?", confirmLabel: "Delete", danger: true }))) return;
    setRows(prev => prev.filter(r => r.id !== row.id));
    try {
      await tapeSend(apiBase() + "/api/ratings/" + row.id, { method: "DELETE" });
      toast.success("Rating deleted");
    } catch (e) {
      setRows(prev => prev.some(r => r.id === row.id) ? prev : [row, ...prev]);
      toast.error("Couldn't delete — " + e.message);
    }
  }

  const stats = useMemo(() => summarize(rows || []), [rows]);
  const buckets = useMemo(() => dailyBuckets(rows || [], days), [rows, days]);
  const visibleRows = useMemo(() => (rows || []).filter(r =>
    band === "all" ? true
    : band === "positive" ? r.score >= 4
    : band === "passive" ? r.score === 3
    : r.score <= 2
  ), [rows, band]);

  const multiServer = servers.length > 1;          // show a server label on each rating
  const canPickServer = isOwner && multiServer;    // server filter (API honours ?guild_id for the owner only)

  return (
    <div className="ratings">
      <div className="usage-head">
        <p className="sub" style={{ margin: 0 }}>
          Daily platform ratings from {isOwner ? "everyone you've asked" : "your community"}.
          {!isOwner && " Scoped to your server(s)."}
        </p>
        <div className="rating-controls">
          {canPickServer && (
            <select className="rating-select" value={serverId} onChange={e => setServerId(e.target.value)}>
              <option value="all">All servers</option>
              {servers.map(s => <option key={s.guild_id} value={s.guild_id}>{s.name}</option>)}
            </select>
          )}
          <div className="seg">
            {[30, 90, 180, 365].map(d => (
              <button key={d} className={cx("seg-btn", days === d && "on")} onClick={() => setDays(d)}>{d === 365 ? "1Y" : d + "d"}</button>
            ))}
          </div>
        </div>
      </div>

      {isOwner && <RatingConfig servers={servers} />}

      {err && <div className="empty" style={{ padding: 30 }}>Couldn't load ratings — {err}</div>}
      {!err && rows === null && <div className="empty" style={{ padding: 30 }}>Loading…</div>}
      {!err && rows !== null && stats.total === 0 && (
        <div className="empty" style={{ padding: 40 }}>No ratings yet in this window. Once users start rating, results appear here.</div>
      )}

      {!err && rows !== null && stats.total > 0 && (
        <>
          <div className="rating-overview">
            <div className="panel rating-avg">
              <div className="ra-num">{stats.avg.toFixed(1)}<span className="ra-den">/5</span></div>
              <StarMeter value={stats.avg} size={18} />
              <div className="ra-meta mono">{stats.total} rating{stats.total !== 1 ? "s" : ""} · {stats.withComment} with a note</div>
            </div>

            <div className="panel rating-dist">
              <div className="rd-title mono">Distribution</div>
              {[5, 4, 3, 2, 1].map(n => {
                const c = stats.counts[n] || 0;
                const pct = stats.total ? (c / stats.total) * 100 : 0;
                return (
                  <div key={n} className="rating-dist-row">
                    <span className="rdr-label mono">{n}{I("star", { size: 11, fill: "currentColor" })}</span>
                    <div className="rdr-track"><div className="rdr-fill" style={{ width: pct + "%", background: bandColor(n) }} /></div>
                    <span className="rdr-count mono">{c}</span>
                  </div>
                );
              })}
            </div>

            <div className="panel rating-trend-panel">
              <div className="rd-title mono">Daily average</div>
              <div className="rating-trend">
                {buckets.map(b => (
                  <div key={b.k} className="rt-col" title={b.count ? `${b.label}: ${b.avg.toFixed(1)}★ (${b.count})` : `${b.label}: no ratings`}>
                    <div className="rt-bar" style={{ height: (b.count ? Math.max(6, (b.avg / 5) * 100) : 0) + "%", background: b.count ? bandColor(b.avg) : "transparent" }} />
                  </div>
                ))}
              </div>
              <div className="rt-axis mono"><span>{buckets[0]?.label}</span><span>{buckets[buckets.length - 1]?.label}</span></div>
            </div>
          </div>

          <div className="rating-list-head">
            <span className="rlh-title">Individual ratings</span>
            <div className="seg">
              {[["all", "All"], ["positive", "4–5★"], ["passive", "3★"], ["negative", "1–2★"]].map(([v, l]) => (
                <button key={v} className={cx("seg-btn", band === v && "on")} onClick={() => setBand(v)}>{l}</button>
              ))}
            </div>
          </div>

          {visibleRows.length === 0 && <div className="empty" style={{ padding: 24 }}>No ratings in this filter.</div>}
          {visibleRows.map(r => (
            <div key={r.id} className="panel rating-card">
              <div className="rating-card-hdr">
                <StarMeter value={r.score} size={14} />
                <span className="usage-user">
                  <DiscordAvatar url={avatars[r.user_id]} size={18} fallback={<span className="hash">{I("user", { size: 12 })}</span>} />
                  <span>{r.user_name || r.user_id}</span>
                  {r.user_role && <span className="usage-role mono">{r.user_role}</span>}
                </span>
                {multiServer && r.guild_id && <span className="rating-srv mono">{serverName(r.guild_id)}</span>}
                <span className="bug-time mono">{fmtTime(new Date(r.ts).getTime(), { short: true })}</span>
                <button className="icon-btn danger" title="Delete rating" onClick={() => remove(r)}>{I("trash", { size: 14 })}</button>
              </div>
              {r.comment && <div className="rating-comment">{r.comment}</div>}
            </div>
          ))}
        </>
      )}
    </div>
  );
}

Object.assign(window, { RatingPrompt, RatingsTab });
