/* FLOWLINE — panels: drawer, exception detail (+ live resolution actions) */

function fmtDateTime(iso) {
  if (!iso) return "";
  const d = new Date(iso);
  if (isNaN(d)) return "";
  return d.toLocaleString(undefined, {
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });
}

/* ---- risk-tier badge tones (Samsara safety tier mix on exception cards) ---- */
const TIER_TONE = {
  critical: "red",
  high: "orange",
  medium: "yellow",
  low: "neutral",
};

/* ---- compliance case status badge ---- */
const CASE_STATUS_TONE = {
  detected: "neutral",
  contacted: "yellow",
  acknowledged: "orange",
  escalated: "red",
  strike: "red",
  resolved: "green",
};
const CASE_STATUS_LABEL = {
  detected: "Detected",
  contacted: "Contacted",
  acknowledged: "Acknowledged",
  escalated: "Escalated",
  strike: "Strike",
  resolved: "Case resolved",
};

function ComplianceBadge({ caseObj }) {
  if (!caseObj) return null;
  var tone = CASE_STATUS_TONE[caseObj.status] || "neutral";
  var label = CASE_STATUS_LABEL[caseObj.status] || caseObj.status;
  var prevStatus = useRef(caseObj.status);
  var [flash, setFlash] = useState(false);

  useEffect(
    function () {
      if (prevStatus.current !== caseObj.status) {
        prevStatus.current = caseObj.status;
        setFlash(true);
        var tid = setTimeout(function () {
          setFlash(false);
        }, 800);
        return function () {
          clearTimeout(tid);
        };
      }
    },
    [caseObj.status],
  );

  return (
    <span className={"tag " + tone + (flash ? " badge-flash" : "")}>
      ⚖ {label}
    </span>
  );
}

/* A case's lifecycle timestamps get set even when the agent is in dry-run
   (ingestion-only) mode and no real SMS ever leaves the building. Only claim a
   nudge/escalation was sent when at least one logged action was a real send
   (dryRun === false); otherwise the note tells the truth. */
const anyRealSend = (actions) =>
  Array.isArray(actions) && actions.some((a) => !a.dryRun);

const messagesHeaderLabel = (actions) =>
  anyRealSend(actions) ? "Messages sent" : "Messages logged (no SMS sent)";

/* Build the case lifecycle timeline, honestly reflecting whether a real
   notification actually went out. `actions` is the intended-action log. */
const buildCaseTimelineEvents = (c, actions) => {
  const sent = anyRealSend(actions);
  const events = [];
  if (c.createdAt)
    events.push({
      label: "Case opened",
      at: c.createdAt,
      note: "Exception detected — waiting for contact delay.",
    });
  if (c.contactedAt)
    events.push({
      label: "Employee contacted",
      at: c.contactedAt,
      note: sent
        ? "Automated nudge sent via SMS."
        : "Ingestion only — escalation not activated, no SMS sent.",
    });
  if (c.acknowledgedAt)
    events.push({
      label: "Acknowledged",
      at: c.acknowledgedAt,
      note: "Employee acknowledged — working on it.",
    });
  if (c.escalatedAt)
    events.push({
      label: "Escalated",
      at: c.escalatedAt,
      note: sent
        ? c.escalationLevel > 1
          ? "Escalated to level " + c.escalationLevel + "."
          : "Escalated to manager."
        : c.escalationLevel > 1
          ? "Escalation level " +
            c.escalationLevel +
            " reached — ingestion only, no notification sent."
          : "Escalation reached — ingestion only, no notification sent.",
    });
  if (c.resolvedAt)
    events.push({
      label: c.status === "strike" ? "Strike recorded" : "Case resolved",
      at: c.resolvedAt,
      note:
        c.status === "strike"
          ? "Employee did not resolve — HR strike recorded."
          : "Exception cleared — case closed.",
    });
  events.sort((a, b) => a.at.localeCompare(b.at));
  return events;
};

/* ---- compact row for a previous compliance case (expandable) ---- */
function PriorCaseRow({ c, onOpenExc }) {
  const tone = CASE_STATUS_TONE[c.status] || "neutral";
  const label = CASE_STATUS_LABEL[c.status] || c.status;
  const closedAt = c.resolvedAt || c.updatedAt || c.createdAt;
  const excTitle =
    c.ruleId && window.FL && window.FL.humanize
      ? window.FL.humanize(c.ruleId)
      : null;
  const [expanded, setExpanded] = useState(false);
  const [timeline, setTimeline] = useState(null);
  const [timelineErr, setTimelineErr] = useState(null);
  const [timelineLoading, setTimelineLoading] = useState(false);

  const toggle = () => {
    if (!expanded && !timeline && !timelineLoading) {
      setTimelineLoading(true);
      setTimelineErr(null);
      API.agentCaseTimeline(c.id)
        .then((r) => {
          setTimeline(r);
        })
        .catch((e) => {
          setTimelineErr(e.message || "Failed to load timeline");
        })
        .finally(() => {
          setTimelineLoading(false);
        });
    }
    setExpanded(!expanded);
  };

  const intendedActions = timeline && timeline.actions ? timeline.actions : [];
  const events = buildCaseTimelineEvents(c, intendedActions);

  return (
    <div style={{ borderBottom: "1px solid var(--border)" }}>
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: "5px 0",
          flexWrap: "wrap",
        }}
      >
        <button
          className="btn ghost sm"
          style={{
            padding: "0 4px",
            fontSize: 12,
            color: "var(--ink-3)",
            flexShrink: 0,
          }}
          onClick={toggle}
          title={expanded ? "Collapse timeline" : "Expand timeline"}
        >
          {expanded ? "▾" : "▸"}
        </button>
        <Tag tone={tone} style={{ flexShrink: 0 }}>
          ⚖ {label}
        </Tag>
        {excTitle && (
          <span
            style={{
              fontSize: 11,
              fontWeight: 600,
              color: "var(--ink-2)",
              flexShrink: 0,
            }}
          >
            {excTitle}
          </span>
        )}
        <span style={{ fontSize: 11, color: "var(--ink-3)", flexShrink: 0 }}>
          Opened {fmtDateTime(c.createdAt)}
        </span>
        {closedAt && closedAt !== c.createdAt && (
          <span style={{ fontSize: 11, color: "var(--ink-3)", flexShrink: 0 }}>
            · Closed {fmtDateTime(closedAt)}
          </span>
        )}
        {c.strikeCount > 0 && (
          <span
            style={{
              fontSize: 11,
              color: "var(--red)",
              marginLeft: "auto",
              flexShrink: 0,
            }}
          >
            {c.strikeCount} strike{c.strikeCount !== 1 ? "s" : ""}
          </span>
        )}
      </div>
      {expanded && (
        <div style={{ paddingBottom: 10, paddingLeft: 4 }}>
          {timelineLoading && (
            <div
              style={{ fontSize: 12, color: "var(--ink-3)", padding: "4px 0" }}
            >
              Loading timeline…
            </div>
          )}
          {timelineErr && (
            <div
              style={{ color: "var(--red)", fontSize: 12, padding: "4px 0" }}
            >
              {timelineErr}
            </div>
          )}
          {!timelineLoading && !timelineErr && events.length > 0 && (
            <div
              className="route"
              style={{
                marginTop: 6,
                marginBottom: intendedActions.length > 0 ? 10 : 0,
              }}
            >
              {events.map((ev, i) => (
                <div
                  key={i}
                  className={
                    "route-step" + (i === events.length - 1 ? " current" : "")
                  }
                >
                  <div className="route-rail">
                    <span
                      className="route-node"
                      style={{
                        background:
                          i === events.length - 1
                            ? "var(--accent)"
                            : "var(--border-strong)",
                      }}
                    />
                  </div>
                  <div className="route-body">
                    <div className="route-role">{ev.label}</div>
                    <div className="route-name">
                      <span className="route-when">{fmtDateTime(ev.at)}</span>
                    </div>
                    <div className="route-note">{ev.note}</div>
                  </div>
                </div>
              ))}
            </div>
          )}
          {!timelineLoading && !timelineErr && intendedActions.length > 0 && (
            <div>
              <div
                style={{
                  fontSize: 11,
                  fontWeight: 650,
                  color: "var(--ink-3)",
                  textTransform: "uppercase",
                  letterSpacing: ".06em",
                  marginBottom: 6,
                }}
              >
                {messagesHeaderLabel(intendedActions)}
              </div>
              {intendedActions.map((a) => (
                <div key={a.id} className="nudge" style={{ marginBottom: 8 }}>
                  <div className="nudge-tag">
                    <span
                      style={{
                        width: 6,
                        height: 6,
                        borderRadius: 9,
                        background: "var(--accent)",
                        display: "inline-block",
                      }}
                    />
                    Flowline →{" "}
                    {(window.FL.nodeById[a.recipientEntityId] || {}).name ||
                      a.recipientEntityId}
                    {a.dryRun && (
                      <span
                        className="tag neutral"
                        style={{ marginLeft: 6, fontSize: 9.5 }}
                      >
                        dry run
                      </span>
                    )}
                  </div>
                  <div className="nudge-msg">{a.messageBody}</div>
                  <div className="nudge-time">
                    {fmtDateTime(a.createdAt)} · {a.reason || a.channel}
                  </div>
                </div>
              ))}
            </div>
          )}
          {!timelineLoading &&
            !timelineErr &&
            timeline &&
            events.length === 0 &&
            intendedActions.length === 0 && (
              <div
                style={{
                  fontSize: 12,
                  color: "var(--ink-3)",
                  padding: "4px 0",
                }}
              >
                No timeline events recorded for this case.
              </div>
            )}
          {!timelineLoading && c.exceptionId && onOpenExc && (
            <div style={{ marginTop: 8 }}>
              <button
                className="btn ghost sm"
                style={{ fontSize: 11 }}
                onClick={() => onOpenExc(c.exceptionId)}
              >
                View original exception →
              </button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* ---- compliance case detail panel ---- */
function CompliancePanel({ exc, onOpenExc }) {
  const ctx = useContext(ActionContext);
  const caseObj = exc.complianceCase;
  const [timeline, setTimeline] = useState(null);
  const [timelineErr, setTimelineErr] = useState(null);
  const [ackBusy, setAckBusy] = useState(false);
  const [ackErr, setAckErr] = useState(null);
  const [localCase, setLocalCase] = useState(caseObj);
  const [priorCases, setPriorCases] = useState(null);
  const [priorExpanded, setPriorExpanded] = useState(false);

  useEffect(() => {
    setLocalCase(exc.complianceCase);
    setTimeline(null);
    setTimelineErr(null);
    setPriorCases(null);
    setPriorExpanded(false);
  }, [exc.complianceCase && exc.complianceCase.id]);

  const loadTimeline = () => {
    if (!localCase) return;
    setTimelineErr(null);
    API.agentCaseTimeline(localCase.id)
      .then((r) => {
        setTimeline(r);
      })
      .catch((e) => {
        setTimelineErr(e.message || "Failed to load timeline");
      });
  };

  useEffect(() => {
    if (!localCase) return;
    loadTimeline();
    const entityId = localCase.entityId || exc.ownerId;
    if (entityId) {
      API.agentCasesForEntity(entityId, localCase.id)
        .then((r) => {
          const prior = r && r.cases ? r.cases : [];
          setPriorCases(prior);
        })
        .catch(() => {
          setPriorCases([]);
        });
    }
  }, [localCase && localCase.id]);

  const handleAcknowledge = async () => {
    if (!localCase) return;
    setAckBusy(true);
    setAckErr(null);
    try {
      const updated = await API.agentAcknowledge(localCase.id);
      setLocalCase(updated);
      loadTimeline();
    } catch (e) {
      setAckErr(e.message || "Failed to acknowledge");
    } finally {
      setAckBusy(false);
    }
  };

  if (!localCase) return null;

  const status = localCase.status;

  const intendedActions = timeline && timeline.actions ? timeline.actions : [];
  const events = buildCaseTimelineEvents(localCase, intendedActions);

  return (
    <div className="block">
      <div className="block-h">
        Compliance case
        <span style={{ marginLeft: 8 }}>
          <ComplianceBadge caseObj={localCase} />
        </span>
      </div>

      {status === "contacted" && ctx.canWrite !== false && (
        <div style={{ marginBottom: 10 }}>
          {ackErr && (
            <div style={{ color: "var(--red)", fontSize: 12, marginBottom: 6 }}>
              {ackErr}
            </div>
          )}
          <button
            className="btn primary sm"
            disabled={ackBusy}
            onClick={handleAcknowledge}
          >
            {ackBusy ? "Acknowledging…" : "Acknowledge case"}
          </button>
          <span style={{ fontSize: 11, color: "var(--ink-3)", marginLeft: 10 }}>
            Confirm you're aware and working on it.
          </span>
        </div>
      )}

      {events.length > 0 && (
        <div
          className="route"
          style={{ marginBottom: intendedActions.length > 0 ? 10 : 0 }}
        >
          {events.map((ev, i) => (
            <div
              key={i}
              className={
                "route-step" + (i === events.length - 1 ? " current" : "")
              }
            >
              <div className="route-rail">
                <span
                  className="route-node"
                  style={{
                    background:
                      i === events.length - 1
                        ? "var(--accent)"
                        : "var(--border-strong)",
                  }}
                />
              </div>
              <div className="route-body">
                <div className="route-role">{ev.label}</div>
                <div className="route-name">
                  <span className="route-when">{fmtDateTime(ev.at)}</span>
                </div>
                <div className="route-note">{ev.note}</div>
              </div>
            </div>
          ))}
        </div>
      )}

      {timelineErr && (
        <div style={{ color: "var(--red)", fontSize: 12, marginBottom: 6 }}>
          {timelineErr}
        </div>
      )}

      {intendedActions.length > 0 && (
        <div>
          <div
            style={{
              fontSize: 11,
              fontWeight: 650,
              color: "var(--ink-3)",
              textTransform: "uppercase",
              letterSpacing: ".06em",
              marginBottom: 6,
            }}
          >
            {messagesHeaderLabel(intendedActions)}
          </div>
          {intendedActions.map((a) => (
            <div key={a.id} className="nudge" style={{ marginBottom: 8 }}>
              <div className="nudge-tag">
                <span
                  style={{
                    width: 6,
                    height: 6,
                    borderRadius: 9,
                    background: "var(--accent)",
                    display: "inline-block",
                  }}
                />
                Flowline →{" "}
                {(window.FL.nodeById[a.recipientEntityId] || {}).name ||
                  a.recipientEntityId}
                {a.dryRun && (
                  <span
                    className="tag neutral"
                    style={{ marginLeft: 6, fontSize: 9.5 }}
                  >
                    dry run
                  </span>
                )}
              </div>
              <div className="nudge-msg">{a.messageBody}</div>
              <div className="nudge-time">
                {fmtDateTime(a.createdAt)} · {a.reason || a.channel}
              </div>
            </div>
          ))}
        </div>
      )}

      {!timeline && !timelineErr && (
        <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
          Loading timeline…
        </div>
      )}

      {priorCases && priorCases.length > 0 && (
        <div
          style={{
            marginTop: 12,
            borderTop: "1px solid var(--border)",
            paddingTop: 10,
          }}
        >
          <button
            className="btn ghost sm"
            style={{ fontSize: 11, padding: "2px 0", color: "var(--ink-3)" }}
            onClick={() => setPriorExpanded(!priorExpanded)}
          >
            {priorExpanded ? "▾" : "▸"} {priorCases.length} prior case
            {priorCases.length !== 1 ? "s" : ""} for this employee
          </button>
          {priorExpanded && (
            <div style={{ marginTop: 8 }}>
              {priorCases.map((c) => (
                <PriorCaseRow key={c.id} c={c} onOpenExc={onOpenExc} />
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* ---- live resolution action bar ---- */

/* Snooze duration choices; value "" = use the server default. */
const SNOOZE_CHOICES = [
  { label: "Default", ms: "" },
  { label: "1 hour", ms: String(3600000) },
  { label: "4 hours", ms: String(4 * 3600000) },
  { label: "1 day", ms: String(24 * 3600000) },
  { label: "3 days", ms: String(3 * 24 * 3600000) },
  { label: "1 week", ms: String(7 * 24 * 3600000) },
];

function ExcActions({ exc }) {
  const ctx = useContext(ActionContext);
  // Inline confirm step: which action is awaiting a note/duration, if any.
  const [confirming, setConfirming] = useState(null);
  const [note, setNote] = useState("");
  const [snoozeMs, setSnoozeMs] = useState("");
  const st = exc.status || "open";
  const samsara = exc.sourceRef && exc.sourceRef.system === "samsara";
  // Manager-facing: Samsara coaching sessions page, filtered to the recorded
  // driver when known (see buildCoachingUrl). Opened alongside acknowledging.
  const coachingUrl = samsara ? exc.sourceRef.coachingUrl : null;
  // Driver-facing: the Samsara Driver Portal, where a tech self-reviews an
  // event a manager has shared with them. The coaching dashboard is
  // manager-only, so techs link here instead (see buildDriverPortalUrl).
  const driverPortalUrl = samsara ? exc.sourceRef.driverPortalUrl : null;
  // Roles the server won't let act (e.g. tech) don't get the resolution
  // controls — no point showing buttons that would only 403, and a tech must
  // never self-approve their own exception. They MAY still self-review in the
  // Samsara Driver Portal, so show a navigation-only link to it (no status
  // change) when one exists, and nothing otherwise. Defaults to the full
  // controls when the context doesn't specify canWrite (isolated tests).
  if (ctx.canWrite === false) {
    if (!driverPortalUrl) return null;
    return (
      <div className="block" style={{ marginBottom: 4 }}>
        <div className="block-h">Coaching</div>
        <div className="exc-actions">
          <a
            className="btn sm"
            href={driverPortalUrl}
            target="_blank"
            rel="noopener noreferrer"
          >
            Review in Samsara Driver Portal
          </a>
        </div>
        <div style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 4 }}>
          Sign in to review this safety event and self-coach. A manager must
          share it with you (“Send to Driver”) before it appears there.
        </div>
      </div>
    );
  }
  const pending = ctx.pendingId === exc.id;
  const run = (action, extra) => ctx.act(exc.id, action, extra);

  const begin = (action) => {
    setConfirming(action);
    setNote("");
    setSnoozeMs("");
  };
  const cancel = () => setConfirming(null);
  const commit = () => {
    const extra = {};
    if (note.trim()) extra.note = note.trim();
    if (confirming === "snooze" && snoozeMs) extra.snoozeMs = Number(snoozeMs);
    if (confirming === "acknowledge" && coachingUrl) {
      window.open(coachingUrl, "_blank", "noopener,noreferrer");
    }
    run(confirming, extra);
    setConfirming(null);
  };

  let stateLine = null;
  if (st === "snoozed") {
    stateLine = (
      <div className="exc-state">
        <Dot sev="yellow" style="filled" size="sm" />
        Snoozed{exc.resolvedByName ? " by " + exc.resolvedByName : ""}
        {exc.resolvedAt ? " · " + fmtDateTime(exc.resolvedAt) : ""} until{" "}
        {fmtDateTime(exc.snoozeUntil) || "later"}.
      </div>
    );
  } else if (st === "resolved") {
    stateLine = (
      <div className="exc-state">
        <Dot sev="green" style="filled" size="sm" />
        Resolved{exc.resolvedByName ? " by " + exc.resolvedByName : ""}
        {exc.resolvedAt ? " · " + fmtDateTime(exc.resolvedAt) : ""}.
      </div>
    );
  } else if (st === "acknowledged") {
    stateLine = (
      <div className="exc-state">
        <Dot sev="orange" style="filled" size="sm" />
        Acknowledged{exc.resolvedByName ? " by " + exc.resolvedByName : ""}
        {exc.resolvedAt ? " · " + fmtDateTime(exc.resolvedAt) : ""} — being
        worked.
      </div>
    );
  }

  return (
    <div className="block" style={{ marginBottom: 4 }}>
      <div className="block-h">Resolution</div>
      {stateLine}
      {exc.resolutionNote && (
        <div className="exc-state" style={{ color: "var(--ink-3)" }}>
          “{exc.resolutionNote}”
        </div>
      )}
      <div className="exc-actions">
        {st === "open" && (
          <>
            <button
              className="btn sm"
              disabled={pending}
              title={
                coachingUrl
                  ? "Acknowledge this exception and open this driver's Samsara coaching sessions"
                  : undefined
              }
              onClick={() => begin("acknowledge")}
            >
              {coachingUrl ? "Samsara Coaching" : "Acknowledge"}
            </button>
            <button
              className="btn sm"
              disabled={pending}
              onClick={() => begin("snooze")}
            >
              Snooze
            </button>
            <button
              className="btn primary sm"
              disabled={pending}
              onClick={() => begin("resolve")}
            >
              Resolve
            </button>
          </>
        )}
        {st === "acknowledged" && (
          <>
            <button
              className="btn sm"
              disabled={pending}
              onClick={() => begin("snooze")}
            >
              Snooze
            </button>
            <button
              className="btn primary sm"
              disabled={pending}
              onClick={() => begin("resolve")}
            >
              Resolve
            </button>
            <button
              className="btn ghost sm"
              disabled={pending}
              onClick={() => begin("reopen")}
            >
              Reopen
            </button>
          </>
        )}
        {(st === "snoozed" || st === "resolved") && (
          <button
            className="btn sm"
            disabled={pending}
            onClick={() => begin("reopen")}
          >
            Reopen
          </button>
        )}
        {pending && (
          <span className="route-when" style={{ alignSelf: "center" }}>
            working…
          </span>
        )}
      </div>
      {confirming && !pending && (
        <div
          className="exc-confirm"
          style={{
            display: "flex",
            flexWrap: "wrap",
            gap: 6,
            alignItems: "center",
            marginTop: 6,
          }}
        >
          {confirming === "snooze" && (
            <select
              className="btn sm"
              aria-label="Snooze duration"
              value={snoozeMs}
              onChange={(e) => setSnoozeMs(e.target.value)}
            >
              {SNOOZE_CHOICES.map((c) => (
                <option key={c.label} value={c.ms}>
                  {c.label}
                </option>
              ))}
            </select>
          )}
          {confirming === "acknowledge" && coachingUrl && (
            <div
              style={{ fontSize: 11, color: "var(--ink-3)", flexBasis: "100%" }}
            >
              Confirming acknowledges this exception and opens this driver's
              Samsara coaching sessions in a new tab.
            </div>
          )}
          <input
            type="text"
            className="input sm"
            style={{ flex: "1 1 140px", minWidth: 120 }}
            aria-label="Resolution note"
            placeholder="Add a note (optional)"
            maxLength={280}
            value={note}
            onChange={(e) => setNote(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") commit();
              if (e.key === "Escape") cancel();
            }}
            autoFocus
          />
          <button className="btn primary sm" onClick={commit}>
            Confirm
          </button>
          <button className="btn ghost sm" onClick={cancel}>
            Cancel
          </button>
        </div>
      )}
    </div>
  );
}

/* ---- prior case history block (no active compliance case) ---- */
function PriorHistoryBlock({ entityId, onOpenExc }) {
  const [cases, setCases] = useState(null);
  const [err, setErr] = useState(null);

  useEffect(() => {
    if (!entityId) return;
    API.agentCasesForEntity(entityId)
      .then((r) => {
        setCases(r && r.cases ? r.cases : []);
      })
      .catch(() => {
        setErr("Failed to load case history");
      });
  }, [entityId]);

  if (!cases && !err)
    return (
      <div className="block">
        <div className="block-h">Prior cases for this employee</div>
        <div style={{ fontSize: 12, color: "var(--ink-3)" }}>Loading…</div>
      </div>
    );
  if (err)
    return (
      <div className="block">
        <div className="block-h">Prior cases for this employee</div>
        <div style={{ fontSize: 12, color: "var(--red)" }}>{err}</div>
      </div>
    );
  if (!cases || cases.length === 0) return null;
  return (
    <div className="block">
      <div className="block-h">Prior cases for this employee</div>
      {cases.map((c) => (
        <PriorCaseRow key={c.id} c={c} onOpenExc={onOpenExc} />
      ))}
    </div>
  );
}

/* ---- one fully-expanded exception ---- */
/* "Find in Samsara" — cross-reference block for source-linked exceptions
   (Samsara safety events today). Surfaces the vehicle, the driver as SAMSARA
   recorded it, the event time, and a dashboard deep link — and flags when
   Flowline's truck-assignment attribution disagrees with Samsara's driver, so
   the manager can trust who to actually follow up with. */
function SamsaraRefBlock({ exc }) {
  const sr = exc.sourceRef;
  if (!sr) return null;
  const FL = window.FL;
  const attributed =
    (FL.peopleById[exc.ownerId] && FL.peopleById[exc.ownerId].name) || null;
  const samsaraDriver = sr.driverName || null;
  const norm = (s) =>
    String(s || "")
      .trim()
      .toLowerCase();
  const mismatch =
    samsaraDriver && attributed && norm(samsaraDriver) !== norm(attributed);
  const warn = { marginTop: 8, fontSize: 12, color: "var(--orange-ink)" };
  const strong = { color: "var(--ink-1)" };
  return (
    <div
      className="block"
      style={{
        border: "1px solid var(--accent-border, var(--border-strong))",
        background: "var(--accent-soft, var(--surface-2))",
        borderRadius: 8,
        padding: "10px 12px",
      }}
    >
      <div className="block-h" style={{ marginBottom: 6 }}>
        Find in Samsara
      </div>
      <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.7 }}>
        <div>
          Vehicle: <b style={strong}>{sr.vehicleName || sr.vehicleId || "—"}</b>
        </div>
        <div>
          Driver (per Samsara):{" "}
          <b style={strong}>{samsaraDriver || "not recorded by Samsara"}</b>
        </div>
        <div>When: {fmtDateTime(sr.occurredAt) || "—"}</div>
      </div>
      {mismatch && (
        <div style={warn}>
          ⚠ Flowline attributed this to <b>{attributed}</b> by truck assignment,
          but Samsara recorded the driver as <b>{samsaraDriver}</b>. Verify who
          actually drove before following up.
        </div>
      )}
      {!samsaraDriver && (
        <div style={warn}>
          ⚠ Samsara didn't record a driver on this event. Flowline attributed it
          to <b>{attributed || "the vehicle's assignee"}</b> by truck assignment
          — check Samsara for who actually drove.
        </div>
      )}
      {sr.url ? (
        <a
          href={sr.url}
          target="_blank"
          rel="noopener noreferrer"
          className="btn ghost sm"
          style={{ marginTop: 10, fontSize: 11.5, display: "inline-block" }}
        >
          Open in Samsara ↗
        </a>
      ) : (
        <div style={{ marginTop: 8, fontSize: 11, color: "var(--ink-3)" }}>
          No dashboard link configured — in Samsara, open Safety → Event
          Resolution and filter by this vehicle and time.
        </div>
      )}
    </div>
  );
}

function ExceptionCard({ exc, defaultOpen, onOpenExc, onFilterByRule }) {
  const [open, setOpen] = useState(defaultOpen ?? true);
  const [showDetails, setShowDetails] = useState(false);
  const [showPlaybook, setShowPlaybook] = useState(false);
  const playbook =
    typeof window.playbookFor === "function"
      ? window.playbookFor(exc.ruleId)
      : [];
  const sev = exc.current;
  const priorCount = exc.priorCaseCount || 0;

  const openToPrior = (e) => {
    e.stopPropagation();
    setOpen(true);
    setShowDetails(true);
  };

  return (
    <div
      className="card"
      style={{
        overflow: "hidden",
        marginBottom: 14,
        borderLeft: `3px solid var(--${sev})`,
      }}
    >
      <button
        onClick={() => setOpen(!open)}
        style={{
          all: "unset",
          cursor: "pointer",
          display: "flex",
          width: "100%",
          boxSizing: "border-box",
          alignItems: "flex-start",
          gap: 10,
          padding: "12px 14px",
        }}
      >
        <Dot sev={sev} style="filled" pulse={sev === "red"} />
        <div style={{ flex: 1 }}>
          <div style={{ fontWeight: 650, fontSize: 13.5 }}>{exc.title}</div>
          {exc.reason && (
            <div
              style={{
                fontSize: 12,
                color: "var(--ink-2)",
                marginTop: 2,
                marginBottom: 3,
                lineHeight: 1.35,
              }}
            >
              {exc.reason.length > 90
                ? exc.reason.slice(0, 90) + "…"
                : exc.reason}
            </div>
          )}
          <div
            style={{
              display: "flex",
              gap: 8,
              alignItems: "center",
              marginTop: 5,
              flexWrap: "wrap",
            }}
          >
            <SevTag sev={sev} />
            <span className="route-when">
              {fmtElapsed(exc.elapsedMin)} open
            </span>
            {exc.status && exc.status !== "open" && (
              <Tag
                tone={
                  exc.status === "resolved"
                    ? "green"
                    : exc.status === "snoozed"
                      ? "yellow"
                      : "orange"
                }
              >
                {exc.status}
              </Tag>
            )}
            {exc.complianceCase && (
              <ComplianceBadge caseObj={exc.complianceCase} />
            )}
            {exc.tierMix &&
              exc.tierMix.length > 0 &&
              exc.tierMix.slice(0, 2).map(function (m) {
                return (
                  <span
                    key={m.label}
                    className={"tag " + (TIER_TONE[m.tier] || "neutral")}
                    title={m.label + " — " + m.tier + " risk"}
                  >
                    {m.label}
                  </span>
                );
              })}
            {priorCount > 0 && (
              <span
                className="tag red"
                style={{ cursor: "pointer" }}
                title={
                  "Open to see " +
                  priorCount +
                  " prior case" +
                  (priorCount !== 1 ? "s" : "") +
                  " for this employee"
                }
                onClick={openToPrior}
              >
                ⚠ {priorCount} prior {priorCount === 1 ? "case" : "cases"}
              </span>
            )}
          </div>
        </div>
        <span
          style={{
            color: "var(--ink-3)",
            transform: open ? "rotate(90deg)" : "none",
            transition: "transform .15s",
            fontSize: 16,
          }}
        >
          ›
        </span>
      </button>
      {open && (
        <div style={{ padding: "0 14px 16px" }}>
          <div className="block">
            <div className="block-h">What's wrong</div>
            <div
              style={{ fontSize: 13, color: "var(--ink-1)", lineHeight: 1.5 }}
            >
              {exc.plainSummary || exc.title}
            </div>
          </div>

          <div
            className="block"
            style={{
              display: "flex",
              gap: 6,
              flexWrap: "wrap",
              alignItems: "center",
              fontSize: 12.5,
              color: "var(--ink-2)",
            }}
          >
            <span>
              Now with{" "}
              <b>
                {exc.currentOwnerName ||
                  (exc.route.find((r) => r.status === "current") || {}).name ||
                  "the owner"}
              </b>
            </span>
            <span style={{ color: "var(--ink-3)" }}>
              · open {fmtElapsed(exc.elapsedMin)}
            </span>
          </div>

          {exc.sourceRef && <SamsaraRefBlock exc={exc} />}

          <ExcActions exc={exc} />

          {playbook.length > 0 && (
            <div style={{ marginTop: 10 }}>
              <button
                className="btn ghost sm"
                style={{ fontSize: 11.5 }}
                onClick={() => setShowPlaybook(!showPlaybook)}
                title="Step-by-step: what to do about this exception"
              >
                {showPlaybook
                  ? "Hide how to handle this ▴"
                  : "How to handle this ▾"}
              </button>
              {showPlaybook && (
                <ol
                  style={{
                    margin: "10px 0 2px",
                    padding: 0,
                    listStyle: "none",
                    counterReset: "pb",
                  }}
                >
                  {playbook.map(function (step, i) {
                    return (
                      <li
                        key={i}
                        style={{
                          position: "relative",
                          paddingLeft: 26,
                          marginBottom: 7,
                          fontSize: 13,
                          lineHeight: 1.5,
                          color: "var(--ink-1)",
                          counterIncrement: "pb",
                        }}
                      >
                        <span
                          style={{
                            position: "absolute",
                            left: 0,
                            top: 1,
                            width: 17,
                            height: 17,
                            borderRadius: "50%",
                            background: "var(--accent-soft, var(--surface-2))",
                            border:
                              "1px solid var(--accent-border, var(--border-strong))",
                            color: "var(--accent)",
                            fontSize: 10,
                            fontWeight: 700,
                            display: "flex",
                            alignItems: "center",
                            justifyContent: "center",
                          }}
                        >
                          {i + 1}
                        </span>
                        {step}
                      </li>
                    );
                  })}
                </ol>
              )}
            </div>
          )}

          <button
            className="btn ghost sm"
            style={{ marginTop: 10, fontSize: 11.5 }}
            onClick={() => setShowDetails(!showDetails)}
            title="Show the full technical detail, rule, escalation route and case history"
          >
            {showDetails ? "Hide details ▴" : "Show details ▾"}
          </button>

          {showDetails && (
            <div style={{ marginTop: 12 }}>
              {exc.tierMix && exc.tierMix.length > 0 && (
                <div
                  className="block"
                  style={{ display: "flex", gap: 8, flexWrap: "wrap" }}
                >
                  {exc.tierMix.map(function (m) {
                    return (
                      <span
                        key={m.label}
                        className={"tag " + (TIER_TONE[m.tier] || "neutral")}
                        title={m.label + " — " + m.tier + " risk"}
                      >
                        {m.label} · {m.tier}
                      </span>
                    );
                  })}
                </div>
              )}

              <div className="block">
                <div className="block-h">Full detail</div>
                <div
                  style={{
                    fontSize: 12.5,
                    color: "var(--ink-2)",
                    lineHeight: 1.5,
                  }}
                >
                  {exc.detail}
                </div>
              </div>

              <div className="block">
                <div className="block-h">Rule</div>
                <div
                  style={{
                    display: "flex",
                    flexWrap: "wrap",
                    alignItems: "center",
                    gap: 8,
                    marginBottom: exc.ruleDescription ? 7 : 0,
                  }}
                >
                  {onFilterByRule ? (
                    <button
                      className="code-pill"
                      style={{
                        fontSize: 11,
                        cursor: "pointer",
                        border: "1px solid var(--border-strong)",
                        background: "var(--surface-2)",
                      }}
                      onClick={function () {
                        onFilterByRule(exc.ruleId);
                      }}
                      title={"See all open exceptions for this rule"}
                    >
                      {exc.code}
                    </button>
                  ) : (
                    <span className="code-pill" style={{ fontSize: 11 }}>
                      {exc.code}
                    </span>
                  )}
                  {exc.ruleSources &&
                    exc.ruleSources.length > 0 &&
                    exc.ruleSources.map(function (s) {
                      return (
                        <span
                          key={s}
                          className="tag neutral"
                          style={{ fontSize: 11 }}
                        >
                          {s}
                        </span>
                      );
                    })}
                  {onFilterByRule && (
                    <button
                      className="btn ghost sm"
                      style={{ fontSize: 11 }}
                      onClick={function () {
                        onFilterByRule(exc.ruleId);
                      }}
                      title={"Filter list to all " + exc.title + " exceptions"}
                    >
                      {exc.title} — see all{" "}
                      {window.FL && window.FL.EXC
                        ? window.FL.EXC.filter(function (x) {
                            return (
                              x.ruleId === exc.ruleId && x.status === "open"
                            );
                          }).length
                        : 0}{" "}
                      →
                    </button>
                  )}
                </div>
                {exc.ruleDescription && (
                  <div
                    style={{
                      fontSize: 12,
                      color: "var(--ink-3)",
                      lineHeight: 1.55,
                      marginTop: 2,
                    }}
                  >
                    {exc.ruleDescription}
                  </div>
                )}
              </div>

              <div className="block">
                <div className="block-h">Live escalation clock</div>
                <EscalationClock exc={exc} />
              </div>

              <div className="block">
                <div className="block-h">Auto-reminder sent</div>
                <div className="nudge">
                  <div className="nudge-tag">
                    <span
                      style={{
                        width: 6,
                        height: 6,
                        borderRadius: 9,
                        background: "var(--accent)",
                        display: "inline-block",
                      }}
                    />
                    Flowline → {exc.route[0] ? exc.route[0].name : "owner"}
                  </div>
                  <div className="nudge-msg">{exc.nudge}</div>
                  <div className="nudge-time">Sent automatically.</div>
                </div>
              </div>

              <div className="block">
                <div className="block-h">Escalation route</div>
                <RouteTimeline route={exc.route} />
              </div>

              {exc.complianceCase && (
                <CompliancePanel exc={exc} onOpenExc={onOpenExc} />
              )}
              {!exc.complianceCase && priorCount > 0 && (
                <PriorHistoryBlock
                  entityId={exc.ownerId}
                  onOpenExc={onOpenExc}
                />
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* =====================  DRAWER  ===================== */
function Drawer({
  sel,
  t,
  onClose,
  onOpenExc,
  onOpenPerson,
  onOpenDay,
  onFilterByRule,
}) {
  const FL = window.FL;
  if (!sel) return null;
  let body = null,
    head = null;

  if (sel.kind === "person") {
    const p = FL.peopleById[sel.id];
    if (!p) return null;
    const ps = FL.personStatus(p.id);
    head = (
      <DrawerHead
        kind="person"
        sev={ps.sev}
        t={t}
        title={p.name}
        sub={p.role}
        onClose={onClose}
      />
    );
    body = (
      <div className="block">
        <div className="block-h">
          {ps.exceptions.length
            ? `Open exceptions · ${ps.exceptions.length}`
            : "Status"}
        </div>
        {ps.exceptions.length === 0 && (
          <div
            className="card"
            style={{
              padding: 16,
              display: "flex",
              gap: 10,
              alignItems: "center",
            }}
          >
            <Dot sev="green" style="filled" />
            <div>
              <b>All clear.</b>{" "}
              <span style={{ color: "var(--ink-3)" }}>
                Nothing needs attention.
              </span>
            </div>
          </div>
        )}
        {ps.exceptions.map((x) => (
          <ExceptionCard
            key={x.id}
            exc={x}
            defaultOpen={ps.exceptions.length === 1}
            onOpenExc={onOpenExc}
            onFilterByRule={onFilterByRule}
          />
        ))}
      </div>
    );
  } else if (sel.kind === "exc") {
    const x = FL.excById(sel.id);
    if (!x) return null;
    const ownerUnit = FL.unitById[x.ownerId];
    const owner = FL.peopleById[x.ownerId] || ownerUnit;
    const ownerKind = FL.peopleById[x.ownerId]
      ? "person"
      : ownerUnit && ownerUnit.type === "department"
        ? "dept"
        : "shop";
    head = (
      <DrawerHead
        kind={ownerKind}
        sev={x.current}
        t={t}
        title={x.title}
        sub={owner ? owner.name : ""}
        onClose={onClose}
      />
    );
    body = (
      <ExceptionCard
        exc={x}
        defaultOpen={true}
        onOpenExc={onOpenExc}
        onFilterByRule={onFilterByRule}
      />
    );
  } else if (
    sel.kind === "shop" ||
    sel.kind === "dept" ||
    sel.kind === "unit"
  ) {
    const u = FL.unitById[sel.id];
    if (!u) return null;
    const st = FL.unitStatus(u.id);
    const kind = u.type === "shop" ? "shop" : "dept";
    const kindWord = u.type === "shop" ? "Shop" : "Department";
    const childUnits = FL.childUnitsOf(u.id);
    const flagged = st.people.filter(
      (p) => FL.personStatus(p.id).sev !== "green",
    );
    head = (
      <DrawerHead
        kind={kind}
        sev={st.sev}
        t={t}
        title={u.name + " — why " + SevText[st.sev].toLowerCase()}
        sub={kindWord + " composite roll-up"}
        onClose={onClose}
      />
    );
    body = (
      <>
        <div className="block">
          <div className="block-h">Roll-up</div>
          <div className="reasons">
            {st.reasons.map((r, i) => (
              <ReasonRow
                key={i}
                r={r}
                onOpen={r.excId ? onOpenExc : undefined}
              />
            ))}
            {st.reasons.length === 0 && (
              <div className="reason">
                <Dot sev="green" style="filled" />
                <div>Everyone in flow. Nothing escalated.</div>
              </div>
            )}
          </div>
          <div
            style={{
              fontSize: 11,
              color: "var(--ink-3)",
              marginTop: 9,
              lineHeight: 1.5,
            }}
          >
            A unit takes on the worst status of its people and sub-teams, plus
            any unit-level operational exceptions.
          </div>
        </div>
        {st.ops.length > 0 && (
          <div className="block">
            <div className="block-h">{kindWord} operational exceptions</div>
            {st.ops.map((x) => (
              <ExceptionCard
                key={x.id}
                exc={x}
                defaultOpen={true}
                onOpenExc={onOpenExc}
                onFilterByRule={onFilterByRule}
              />
            ))}
          </div>
        )}
        {childUnits.length > 0 && (
          <div className="block">
            <div className="block-h">Groups · {childUnits.length}</div>
            <div className="reasons">
              {childUnits.map((cu) => {
                const cs = FL.unitStatus(cu.id);
                return (
                  <div key={cu.id} className="reason">
                    <Dot sev={cs.sev} style="filled" />
                    <div style={{ flex: 1 }}>
                      <b>{cu.name}</b>{" "}
                      <span style={{ color: "var(--ink-3)" }}>
                        · {SevText[cs.sev]} · {FL.peopleInUnit(cu.id).length}{" "}
                        people
                      </span>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}
        {st.people.length > 0 && (
          <div className="block">
            <div className="block-h">Flagged people · {flagged.length}</div>
            {flagged.length === 0 && (
              <div className="reason">
                <Dot sev="green" style="filled" />
                <div>No one flagged.</div>
              </div>
            )}
            <div className="reasons">
              {flagged.map((p) => {
                const psv = FL.personStatus(p.id);
                return (
                  <div
                    key={p.id}
                    className="reason click"
                    onClick={() => onOpenPerson(p.id)}
                  >
                    <Dot sev={psv.sev} style="filled" />
                    <div style={{ flex: 1 }}>
                      <b>{p.name}</b>{" "}
                      <span style={{ color: "var(--ink-3)" }}>
                        · {psv.reasons[0] ? psv.reasons[0].text : p.role}
                      </span>
                    </div>
                    <span style={{ color: "var(--ink-3)" }}>›</span>
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </>
    );
  }

  return (
    <>
      <div className="drawer-scrim" onClick={onClose} />
      <div className="drawer" role="dialog">
        {head}
        <div className="drawer-body">{body}</div>
      </div>
    </>
  );
}

function DrawerHead({ kind, sev, t, title, sub, onClose }) {
  return (
    <div className="drawer-head">
      <Avatar
        kind={kind}
        sev={sev}
        dotStyle={t.dotStyle}
        size={42}
        square={kind !== "person"}
        pulse={t.pulse}
      />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="drawer-title">{title}</div>
        <div className="drawer-sub">{sub}</div>
      </div>
      <button className="btn ghost sm drawer-x" onClick={onClose}>
        ✕
      </button>
    </div>
  );
}

/* =====================  PERSON DAY VIEW  ===================== */
function PersonDay({ personId, t, onSelectExc, onFilterByRule }) {
  const FL = window.FL;
  const p = FL.peopleById[personId];
  if (!p)
    return (
      <div className="canvas">
        <div className="empty">Person not found.</div>
      </div>
    );
  const ps = FL.personStatus(personId);
  return (
    <div className="canvas" style={{ maxWidth: 880 }}>
      <div className="view-head">
        <div style={{ display: "flex", gap: 14, alignItems: "center" }}>
          <Avatar
            kind="person"
            sev={ps.sev}
            dotStyle={t.dotStyle}
            size={48}
            pulse={t.pulse}
          />
          <div>
            <div className="view-title">{p.name}</div>
            <div className="view-desc">
              {p.role} ·{" "}
              {FL.unitById[p.unitId] ? FL.unitById[p.unitId].name : ""}
            </div>
          </div>
        </div>
        <div style={{ marginLeft: "auto" }}>
          <SevTag sev={ps.sev} />
        </div>
      </div>

      {ps.exceptions.length === 0 ? (
        <div className="card">
          <div className="empty">All clear — nothing needs attention.</div>
        </div>
      ) : (
        <>
          <div className="section-label">
            Open exceptions on {p.name.split(" ")[0]} · {ps.exceptions.length}
          </div>
          {ps.exceptions.map((x) => (
            <ExceptionCard
              key={x.id}
              exc={x}
              defaultOpen={ps.exceptions.length === 1}
              onOpenExc={onSelectExc}
              onFilterByRule={onFilterByRule}
            />
          ))}
        </>
      )}

      {/* Coaching history — patterns the coaching engine has flagged for this
          person. Reads FL.COACHING (populated at load time) so it stays fresh
          after auto-refresh. Only shown when there is at least one pattern. */}
      {(function () {
        var coachingRows = (FL.COACHING || []).filter(function (c) {
          return c.entityId === personId;
        });
        if (!coachingRows.length) return null;
        return (
          <div style={{ marginTop: 22 }}>
            <div className="section-label">
              Coaching patterns · {coachingRows.length}
            </div>
            <div className="card" style={{ padding: 4 }}>
              <table className="admin-table">
                <thead>
                  <tr>
                    <th>Rule</th>
                    <th style={{ textAlign: "right" }}>Count (window)</th>
                    <th style={{ textAlign: "right" }}>Prior window</th>
                    <th style={{ textAlign: "right" }}>Trend</th>
                  </tr>
                </thead>
                <tbody>
                  {coachingRows.map(function (c) {
                    var trendIcon =
                      c.trend === "rising"
                        ? "↑"
                        : c.trend === "falling"
                          ? "↓"
                          : "→";
                    var trendColor =
                      c.trend === "rising"
                        ? "var(--red)"
                        : c.trend === "falling"
                          ? "var(--green)"
                          : "var(--ink-3)";
                    return (
                      <tr key={c.ruleId}>
                        <td>
                          <span style={{ fontWeight: 600, fontSize: 13 }}>
                            {FL.humanize ? FL.humanize(c.ruleId) : c.ruleId}
                          </span>
                        </td>
                        <td style={{ textAlign: "right", fontWeight: 650 }}>
                          {c.currentCount}×
                        </td>
                        <td
                          style={{
                            textAlign: "right",
                            color: "var(--ink-3)",
                            fontSize: 12,
                          }}
                        >
                          {c.priorCount > 0 ? c.priorCount + "×" : "—"}
                        </td>
                        <td style={{ textAlign: "right" }}>
                          <span
                            style={{
                              fontWeight: 600,
                              fontSize: 12,
                              color: trendColor,
                            }}
                          >
                            {trendIcon} {c.trend}
                          </span>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

/* =====================  RULE EXCEPTIONS VIEW (flat list, filterable)  =====================
 * When ruleId is a non-empty string: pre-filtered to that rule (navigated via
 * "see all → " button in an exception card).
 * When ruleId is "" (empty string): shows all exceptions with filter controls
 * (rule dropdown, severity, keyword search) and a rule-count breakdown header.
 * ======================================================================================= */
function RuleExceptionsView({ ruleId, onClear, onOpenExc }) {
  var FL = window.FL;
  var sevOrd = { red: 0, orange: 1, yellow: 2, green: 3 };

  // Local filter state — supplements the global ruleId from the shell.
  var [search, setSearch] = useState("");
  var [localSev, setLocalSev] = useState("");
  // localRule only applies when the parent ruleId is "" (all-exceptions mode).
  var [localRule, setLocalRule] = useState("");

  // Sync localRule when the parent ruleId changes so navigating between rules
  // from exception card chips doesn't leave a stale local override.
  useEffect(
    function () {
      if (ruleId) setLocalRule("");
    },
    [ruleId],
  );

  // Effective rule: explicit ruleId from shell takes priority over local dropdown.
  var effectiveRule = ruleId || localRule || null;

  var title = effectiveRule
    ? FL.humanize
      ? FL.humanize(effectiveRule)
      : String(effectiveRule)
    : "All exceptions";

  // Build candidate set (pre-rule filter).
  var base = effectiveRule
    ? FL.EXC.filter(function (x) {
        return x.ruleId === effectiveRule;
      })
    : FL.EXC.slice();

  // Apply severity and text filters.
  var trimSearch = search.trim().toLowerCase();
  if (localSev) {
    base = base.filter(function (x) {
      return x.current === localSev;
    });
  }
  if (trimSearch) {
    base = base.filter(function (x) {
      var ownerNode = FL.nodeById[x.ownerId];
      return (
        (x.title || "").toLowerCase().includes(trimSearch) ||
        (x.detail || "").toLowerCase().includes(trimSearch) ||
        (ownerNode ? ownerNode.name.toLowerCase() : "").includes(trimSearch) ||
        (x.currentOwnerName || "").toLowerCase().includes(trimSearch)
      );
    });
  }

  var matching = base.slice().sort(function (a, b) {
    var da = sevOrd[a.current] !== undefined ? sevOrd[a.current] : 4;
    var db = sevOrd[b.current] !== undefined ? sevOrd[b.current] : 4;
    if (da !== db) return da - db;
    return (b.elapsedMin || 0) - (a.elapsedMin || 0);
  });

  var ruleDesc =
    matching.length > 0 && effectiveRule ? matching[0].ruleDescription : null;

  // Rule breakdown: count per rule across the *unfiltered-by-rule* set so the
  // header always shows the overall distribution (not just the selected rule).
  var allForBreakdown = FL.EXC.slice();
  if (localSev)
    allForBreakdown = allForBreakdown.filter(function (x) {
      return x.current === localSev;
    });
  if (trimSearch)
    allForBreakdown = allForBreakdown.filter(function (x) {
      var ownerNode = FL.nodeById[x.ownerId];
      return (
        (x.title || "").toLowerCase().includes(trimSearch) ||
        (x.detail || "").toLowerCase().includes(trimSearch) ||
        (ownerNode ? ownerNode.name.toLowerCase() : "").includes(trimSearch) ||
        (x.currentOwnerName || "").toLowerCase().includes(trimSearch)
      );
    });

  var ruleCounts = {};
  allForBreakdown.forEach(function (x) {
    ruleCounts[x.ruleId] = (ruleCounts[x.ruleId] || 0) + 1;
  });
  var ruleCountEntries = Object.keys(ruleCounts)
    .sort(function (a, b) {
      return ruleCounts[b] - ruleCounts[a];
    })
    .map(function (rId) {
      return {
        ruleId: rId,
        count: ruleCounts[rId],
        title: FL.humanize ? FL.humanize(rId) : rId,
      };
    });

  // Rule options for dropdown (sorted alphabetically by display name).
  var ruleOptions = Object.keys(ruleCounts)
    .slice()
    .sort(function (a, b) {
      var ta = FL.humanize ? FL.humanize(a) : a;
      var tb = FL.humanize ? FL.humanize(b) : b;
      return ta.localeCompare(tb);
    });

  var hasFilters = !!(search || localSev || (!ruleId && localRule));

  return (
    <div className="canvas" style={{ maxWidth: 880 }}>
      <div className="view-head">
        <div>
          <div className="view-title">{title}</div>
          <div className="view-desc">
            {matching.length} exception{matching.length !== 1 ? "s" : ""} shown
            {FL.EXC.length !== matching.length
              ? " · " + FL.EXC.length + " total"
              : ""}
          </div>
          {ruleDesc && (
            <div className="view-desc" style={{ marginTop: 4 }}>
              {ruleDesc}
            </div>
          )}
        </div>
        <div style={{ marginLeft: "auto" }}>
          <button className="btn sm" onClick={onClear}>
            ← Back to org view
          </button>
        </div>
      </div>

      {/* ---- filter controls ---- */}
      <div
        style={{
          display: "flex",
          flexWrap: "wrap",
          gap: 8,
          marginBottom: 12,
          alignItems: "center",
        }}
      >
        <input
          type="text"
          className="input sm"
          style={{
            flex: "1 1 180px",
            minWidth: 150,
            height: 30,
            fontSize: 12,
            padding: "0 8px",
          }}
          placeholder="Search by name, rule, or detail…"
          value={search}
          onChange={function (e) {
            setSearch(e.target.value);
          }}
        />
        <select
          className="select"
          style={{ height: 30, fontSize: 12, padding: "0 8px" }}
          value={localSev}
          onChange={function (e) {
            setLocalSev(e.target.value);
          }}
        >
          <option value="">All severities</option>
          <option value="red">Critical</option>
          <option value="orange">Escalated</option>
          <option value="yellow">Flagged</option>
        </select>
        {/* Rule dropdown only shown in all-exceptions mode (ruleId="") */}
        {!ruleId && (
          <select
            className="select"
            style={{
              height: 30,
              fontSize: 12,
              padding: "0 8px",
              maxWidth: 220,
            }}
            value={localRule}
            onChange={function (e) {
              setLocalRule(e.target.value);
            }}
          >
            <option value="">All rules</option>
            {ruleOptions.map(function (rId) {
              return (
                <option key={rId} value={rId}>
                  {FL.humanize ? FL.humanize(rId) : rId} ({ruleCounts[rId]})
                </option>
              );
            })}
          </select>
        )}
        {hasFilters && (
          <button
            className="btn ghost sm"
            style={{ fontSize: 11 }}
            onClick={function () {
              setSearch("");
              setLocalSev("");
              if (!ruleId) setLocalRule("");
            }}
          >
            × Clear filters
          </button>
        )}
      </div>

      {/* ---- rule count breakdown (shown in all-exceptions mode, no rule selected) ---- */}
      {!effectiveRule && ruleCountEntries.length > 0 && (
        <div
          style={{
            display: "flex",
            flexWrap: "wrap",
            gap: 6,
            marginBottom: 14,
          }}
        >
          {ruleCountEntries.map(function (entry) {
            return (
              <button
                key={entry.ruleId}
                className="tag neutral"
                style={{
                  cursor: "pointer",
                  fontSize: 11,
                  border: "1px solid var(--border-strong)",
                  background: "var(--surface-2)",
                }}
                onClick={function () {
                  setLocalRule(entry.ruleId);
                }}
                title={"Show only " + entry.title + " exceptions"}
              >
                {entry.title}: {entry.count}
              </button>
            );
          })}
        </div>
      )}

      {matching.length === 0 ? (
        <div className="card">
          <div className="empty">
            {hasFilters
              ? "No exceptions match these filters."
              : effectiveRule
                ? "No open exceptions for this rule right now."
                : "No open exceptions right now."}
          </div>
        </div>
      ) : (
        matching.map(function (x) {
          return (
            <ExceptionCard
              key={x.id}
              exc={x}
              defaultOpen={matching.length === 1}
              onOpenExc={onOpenExc}
            />
          );
        })
      )}
    </div>
  );
}

Object.assign(window, {
  Drawer,
  ExceptionCard,
  PersonDay,
  ExcActions,
  RuleExceptionsView,
});
