/* Trial Calendar — the view the Content Center lives in to make coverage calls.
   Day, Week and Month views of courtroom activity. Week and Month are the grid
   the team knew from Airtable: multi-day trials render as a single continuous bar
   that crosses the days it covers; single-day events sit on their day. Day is a
   time axis in EASTERN time (L&C's zone) — court times are stored in the court's
   own zone and converted by window.LedgerTime, and every screen says which zone
   it is showing. Colors match the team's current calendar.
   A separate tab lists tracked matters with NO court date set yet — the watchlist
   they keep so they remember to keep checking the court calendar.
   Assigns window.LedgerTrialCalendar. */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const T = window.titleize;
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);

  // Date helpers and the Day/Week/Month navigation come from the shared kit
  // (CalendarKit.jsx); this file keeps what is particular to court activity.
  const CK = window.LedgerCalendarKit;
  const { iso, parseISO, addDays, MONTHS, DOW } = CK;
  const WEEKEND_KEY = "ledger.trialCalendar.showWeekends";
  const MAX_LANES = 4; // event rows shown per week (month view) before a day collapses to "+N"

  // Event type -> the team's existing calendar categories + colors (see .cal-ev--* / .cal-dot--*).
  const CAT_OF = {
    trial_start: "trial", trial_day: "trial",
    hearing: "hearing",
    sentencing: "sentencing",
    bail_hearing: "pretrial",
    arraignment: "arraignment",
    motion_hearing: "motion",
    change_of_plea: "plea",
  };
  const CAT_META = {
    trial: { label: "Trial" }, hearing: { label: "Hearing" }, sentencing: { label: "Sentencing" },
    pretrial: { label: "Pre-trial" }, arraignment: { label: "Arraignment" }, motion: { label: "Motion hearing" },
    plea: { label: "Change of plea" }, other: { label: "Other" },
  };
  const LEGEND = ["trial", "hearing", "sentencing", "plea", "pretrial", "arraignment", "motion", "other"];
  const category = (e) => CAT_OF[e.event_type] || "other";
  // The "Trials & sentencings only" filter keeps the ones the team watches most closely.
  // A change of plea belongs here: it is the day a trial they were planning to cover
  // stops happening, which is exactly when the desk needs to know.
  const isKey = (e) => { const c = category(e); return c === "trial" || c === "sentencing" || c === "plea"; };
  const endOf = (e) => (e.end_date && e.end_date >= e.event_date) ? e.end_date : e.event_date;
  const matterName = (e) => (e.matters && e.matters.working_title) || "Untitled matter";
  // Eastern-time view of an event's start (null when it has no time). Computed once
  // per load in CalendarGrid and carried on the row as `e.et`.
  const withET = (e) => ({ ...e, et: window.LedgerTime.toEastern(e.event_date, e.event_time, e.matters && e.matters.crime_location_state) });
  // Compact time for a bar or list: "11:30 AM" (ET) or "9:00 AM local" when the
  // court's zone is unknown, so it is never passed off as Eastern.
  const etShort = (et) => !et ? "" : et.known ? et.short : et.short + " local";
  const etTitle = (et) => !et ? "" : [et.known ? et.label : null, et.courtLabel ? "court " + et.courtLabel : null, et.note].filter(Boolean).join(" · ");
  const HOUR_PX = 64;       // day view: one hour of the time axis
  const SLOT_MIN = 60;      // an event has no end time; it is drawn as one hour

  // ---- Calendar grid (day, week or month) ---------------------------------
  function CalendarGrid({ onOpenMatter }) {
    // DEFAULT IS THE WEEK (operator, 2026-09-18). This reverses 2026-07-10, when
    // the default moved to Month at the Content Center's request (Sara Burns:
    // "change the default to the month, not the day"). Week is the working horizon
    // for coverage calls; Month is one click away and still the overview.
    // Weekends hidden by default (Lacey, 2026-10-01: "no reason for it"); the choice
    // is remembered per browser. Hidden weekend events are never silently dropped:
    // they roll up into a "+N weekend" marker on Friday.
    const nav = CK.useCalendarNav({ defaultView: "week", weekendKey: WEEKEND_KEY, weekendDefault: false });
    const { view, cursor, isDay, isWeek, showWeekends, setShowWeekends, nCols, colStyle, cells, weeks, dowRow, gridStart } = nav;
    const cellCount = cells.length;
    const [events, setEvents] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [keyOnly, setKeyOnly] = React.useState(false);
    const [coveredOnly, setCoveredOnly] = React.useState(false);
    // "Mine" (0159, B3): matters whose Content Center owner is the signed-in user.
    // Offered to Content Ops only, and only once their staff id resolves.
    const [myStaff, setMyStaff] = React.useState(null);
    const [mineOnly, setMineOnly] = React.useState(false);
    React.useEffect(() => {
      const caps = window.LedgerCaps || {};
      if (caps.isContentOps || caps.isAdmin) window.LedgerAPI.myStaffIdAnyDomain().then(setMyStaff).catch(() => {});
    }, []);
    const isMine = (e) => !!(myStaff && e.matters && e.matters.cc_assignee_staff_id === myStaff);
    const [covDefaults, setCovDefaults] = React.useState({});
    const [selDay, setSelDay] = React.useState(null);
    // Move a court date from the day list (0158): { e, date, end, time, reason, busy, err }.
    const [moving, setMoving] = React.useState(null);
    // Matter-default coverage per matter (id -> {ingest_method, feed_location, ...}),
    // fetched once; lets the calendar denote what we're covering and how.
    React.useEffect(() => { window.LedgerAPI.coverageDefaults().then(setCovDefaults).catch(() => {}); }, []);
    const isCovered = (e) => !!(e.matters && e.matters.coverage_status === "assigned");
    // Covering vs Tracking (B4, Lacey 2026-10-22): Covering is a solid bar in the
    // type's color, everything else (Tracking, adjudicated, unset) is an outline.
    const covCls = (e) => (isCovered(e) ? " is-cov" : " is-trk");
    const covMethod = (e) => { const cd = covDefaults[e.matter_id]; return cd && cd.ingest_method; };
    const methodShort = (window.LedgerCoverageUtil && window.LedgerCoverageUtil.methodShort) || ((m) => T(m));
    // Week view shows every lane (there's vertical room); month caps at MAX_LANES.
    const maxLanes = isWeek ? 99 : MAX_LANES;

    // "Today" is today in EASTERN, the zone the whole calendar is read in.
    const todayISO = nav.today;
    const etAbbr = window.LedgerTime.homeAbbr(iso(cursor));

    const load = React.useCallback(() => {
      setEvents(null); setErr(null);
      window.LedgerAPI.listCalendarEvents(iso(cells[0]), iso(cells[cells.length - 1]))
        .then((rows) => setEvents(rows.map(withET))).catch((e) => setErr(e.message || String(e)));
    }, [iso(gridStart), cellCount]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); }, [events, selDay, keyOnly, coveredOnly, covDefaults, view, moving && moving.e.event_id]);

    const isSpan = (e) => e.event_type === "trial_start" || e.event_type === "trial_day";
    function startMove(e) {
      setMoving({ e, date: e.event_date, end: isSpan(e) ? (e.end_date || "") : "",
                  time: (e.event_time || "").slice(0, 5), reason: "", busy: false, err: null });
    }
    async function saveMove() {
      const m = moving;
      if (!m.date) { setMoving({ ...m, err: "Pick a date." }); return; }
      if (m.end && m.end < m.date) { setMoving({ ...m, err: "End date can’t be before the start date." }); return; }
      setMoving({ ...m, busy: true, err: null });
      try {
        await window.LedgerAPI.moveCaseEvent(m.e.event_id, { date: m.date, endDate: isSpan(m.e) ? m.end : null, time: m.time, reason: m.reason });
        setMoving(null);
        // The event may have left this day (or this view): follow it there.
        if (m.date !== selDay) setSelDay(null);
        load();
      } catch (err) { setMoving({ ...m, busy: false, err: err.message || String(err) }); }
    }

    const shownEvents = React.useMemo(
      () => (events || []).filter((e) => (!keyOnly || isKey(e)) && (!coveredOnly || isCovered(e)) && (!mineOnly || isMine(e))), [events, keyOnly, coveredOnly, mineOnly, myStaff]);

    // Lay one week out: each event becomes a horizontal segment spanning the
    // columns it covers within the week, packed into lanes so they don't overlap.
    function layoutWeek(weekDays) {
      const wStartISO = iso(weekDays[0]), wEndISO = iso(weekDays[6]);
      const segs = [];
      let weekend = 0; // events living only on this week's hidden Sat/Sun
      shownEvents.forEach((e) => {
        const evEndISO = endOf(e);
        if (e.event_date > wEndISO || evEndISO < wStartISO) return; // no overlap with this week
        const segStartISO = e.event_date < wStartISO ? wStartISO : e.event_date;
        const segEndISO = evEndISO > wEndISO ? wEndISO : evEndISO;
        const startCol = weekDays.findIndex((d) => iso(d) === segStartISO);
        let endCol = weekDays.findIndex((d) => iso(d) === segEndISO);
        let contR = evEndISO > wEndISO;
        if (startCol >= nCols) { weekend++; return; }           // Sat/Sun only → Friday marker
        if (endCol >= nCols) { endCol = nCols - 1; contR = true; } // runs into the weekend
        segs.push({ e, startCol, endCol, span: endCol - startCol + 1,
          contL: e.event_date < wStartISO, contR, multi: evEndISO !== e.event_date });
      });
      // Covering first (Lacey 2026-10-15), so what we cover takes the top lanes and is
      // never the one pushed into a month cell's "+N". Then longest/earliest first →
      // stable lanes (a trial keeps the top lane across the week). Then by Eastern
      // start time, so a day's single events read top-to-bottom in the order they happen.
      segs.sort((a, b) => (isCovered(b.e) - isCovered(a.e)) || (b.span - a.span) || (a.startCol - b.startCol) || (isKey(b.e) - isKey(a.e)) || (etMin(a.e) - etMin(b.e)));
      const lanes = [];
      segs.forEach((s) => {
        let lane = 0;
        for (; ; lane++) {
          const occ = lanes[lane] || (lanes[lane] = []);
          if (!occ.some((o) => !(s.endCol < o.startCol || s.startCol > o.endCol))) { occ.push(s); s.lane = lane; break; }
        }
      });
      // Days where some segment was pushed past the visible lanes → show a "+N".
      const hidden = {};
      segs.forEach((s) => { if (s.lane >= maxLanes) for (let c = s.startCol; c <= s.endCol; c++) hidden[c] = (hidden[c] || 0) + 1; });
      return { segs: segs.filter((s) => s.lane < maxLanes), laneCount: Math.min(lanes.length, maxLanes), hidden, weekend };
    }

    // Ordered by EASTERN time — sorting the raw court-local time would put an
    // 8:30 PT hearing (11:30 ET) ahead of a 9:00 ET one.
    const etMin = (e) => (e.et && e.et.known ? e.et.minutes : 1e9);
    const dayEvents = (dayISO) => shownEvents
      .filter((e) => e.event_date <= dayISO && endOf(e) >= dayISO)
      .sort((a, b) => (isCovered(b) - isCovered(a)) || (isKey(b) - isKey(a)) || (etMin(a) - etMin(b)));

    const openDay = (dayISO) => { setSelDay(null); nav.goTo(dayISO); nav.setView("day"); };

    // In week view there's room for the time (Eastern); show it on the bar's start day.
    const barLabel = (s) => {
      const base = (s.contL ? "… " : "") + CAT_META[category(s.e)].label + " · " + matterName(s.e);
      return (isWeek && !s.contL && s.e.et) ? etShort(s.e.et) + " · " + base : base;
    };
    const selEvents = selDay ? dayEvents(selDay) : [];

    return (
      <div>
        <CK.CalendarHeader nav={nav} onNav={() => setSelDay(null)}>
          <label className="cal-keyonly">
            <input type="checkbox" checked={keyOnly} onChange={(e) => setKeyOnly(e.target.checked)} />
            Trials &amp; sentencings only
          </label>
          <label className="cal-keyonly">
            <input type="checkbox" checked={coveredOnly} onChange={(e) => setCoveredOnly(e.target.checked)} />
            Covering only
          </label>
          {myStaff && (
            <label className="cal-keyonly">
              <input type="checkbox" checked={mineOnly} onChange={(e) => setMineOnly(e.target.checked)} />
              Mine
            </label>
          )}
          <label className="cal-keyonly">
            <input type="checkbox" checked={showWeekends} onChange={(e) => setShowWeekends(e.target.checked)} />
            Weekends
          </label>
        </CK.CalendarHeader>

        <div className="cal-legend">
          {LEGEND.map((c) => <span key={c}><i className={"cal-dot cal-dot--" + c} /> {CAT_META[c].label}</span>)}
          <span className="cal-legend-cov"><i className="cal-swatch is-cov" /> Covering <i className="cal-swatch is-trk" /> Tracking</span>
          <span className="cal-tznote" title="Court times are stored in each court's own time zone and converted to Eastern for display.">
            <i data-lucide="clock" /> Times in Eastern ({etAbbr})
          </span>
        </div>

        {err && <div className="login__err" style={{ marginBottom: 14 }}>{err}</div>}

        {isDay && (
          <DayView dayISO={iso(cursor)} events={events === null ? null : dayEvents(iso(cursor))}
                   todayISO={todayISO} etAbbr={etAbbr} onOpenMatter={onOpenMatter}
                   isCovered={isCovered} covMethod={covMethod} methodShort={methodShort} />
        )}

        {!isDay && <div className={"cal-cal" + (isWeek ? " cal-cal--week" : "")}>
          {!isWeek && <div className="cal-dow-row" style={colStyle}>{dowRow.map((d) => <div key={d} className="cal-dow">{d}</div>)}</div>}
          {weeks.map((week, wi) => {
            const { segs, laneCount, hidden, weekend } = layoutWeek(week);
            return (
              <div key={wi} className="cal-week" style={{ "--lanes": laneCount }}>
                <div className="cal-week-bg" style={colStyle}>
                  {week.slice(0, nCols).map((d, ci) => {
                    const k = iso(d);
                    const inMonth = isWeek || d.getMonth() === cursor.getMonth();
                    return (
                      <div key={k} className={"cal-bgcell" + (inMonth ? "" : " cal-bgcell--out") + (k === todayISO ? " cal-bgcell--today" : "") + (k === selDay ? " cal-bgcell--sel" : "")}
                        onClick={() => setSelDay(dayEvents(k).length ? k : null)}>
                        <div className="cal-daynum">{isWeek ? <><span className="cal-dayname">{DOW[d.getDay()]}</span> {d.getDate()}</> : d.getDate()}</div>
                        {hidden[ci] > 0 && <span className="cal-hidden">+{hidden[ci]} more</span>}
                        {!showWeekends && ci === nCols - 1 && weekend > 0 && (
                          <button type="button" className="cal-wkndmark" title="Events on the weekend. Click to show weekends."
                            onClick={(ev) => { ev.stopPropagation(); setShowWeekends(true); }}>+{weekend} weekend</button>
                        )}
                      </div>
                    );
                  })}
                </div>
                <div className="cal-week-fg" style={colStyle}>
                  {segs.map((s, i) => (
                    <button key={i} type="button"
                      className={"cal-ev cal-ev--" + category(s.e) + covCls(s.e) + (s.contL ? " cal-ev--contL" : "") + (s.contR ? " cal-ev--contR" : "")}
                      style={{ gridColumn: `${s.startCol + 1} / ${s.endCol + 2}`, gridRow: s.lane + 1 }}
                      title={(isCovered(s.e) ? "Covering" + (covMethod(s.e) ? " · " + methodShort(covMethod(s.e)) : "") : "Tracking") + " · " + barLabel(s) + (s.multi ? ` (${parseISO(s.e.event_date).toLocaleDateString(undefined, { month: "short", day: "numeric" })} – ${parseISO(endOf(s.e)).toLocaleDateString(undefined, { month: "short", day: "numeric" })})` : "") + (s.e.courthouse || s.e.location ? " — " + (s.e.courthouse || s.e.location) : "")}
                      onClick={(ev) => { ev.stopPropagation(); onOpenMatter && onOpenMatter(s.e.matter_id); }}>
                      <span className="cal-ev-txt">{barLabel(s)}</span>
                    </button>
                  ))}
                </div>
              </div>
            );
          })}
        </div>}

        {events === null && <div style={{ color: "var(--text-muted)", fontSize: 13, marginTop: 14 }}>Loading court activity…</div>}

        {!isDay && selDay && selEvents.length > 0 && (
          <div className="cal-day">
            <div className="cal-day-head">
              <b>{parseISO(selDay).toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric" })}</b>
              <span style={{ display: "inline-flex", gap: 12 }}>
                <button className="link-btn" onClick={() => openDay(selDay)}><i data-lucide="clock" /> Day view</button>
                <button className="link-btn" onClick={() => setSelDay(null)}><i data-lucide="x" /> Close</button>
              </span>
            </div>
            <ul className="linklist">
              {selEvents.map((e, i) => (
                <React.Fragment key={e.event_id || i}>
                <li style={{ cursor: "pointer" }} onClick={() => onOpenMatter && onOpenMatter(e.matter_id)}>
                  <span className={"cal-dot cal-dot--" + category(e)} style={{ marginTop: 4 }} />
                  <div className="li-main">
                    <b>{matterName(e)} {e.matters && e.matters.display_id ? <span className="cal-mid">{e.matters.display_id}</span> : null}
                      {e.matters && e.matters.coverage_status ? <span style={{ marginLeft: 6 }}><window.LedgerCoveragePill status={e.matters.coverage_status} small /></span> : null}</b>
                    <span>
                      {e.judge_name ? e.judge_name + " · " : ""}
                      {T(e.event_type)}
                      {endOf(e) !== e.event_date ? ` (${parseISO(e.event_date).toLocaleDateString(undefined, { month: "short", day: "numeric" })} – ${parseISO(endOf(e)).toLocaleDateString(undefined, { month: "short", day: "numeric" })})` : ""}
                      {e.et && e.event_date === selDay ? " · " + (e.et.known ? e.et.label : etShort(e.et) + " (zone unknown)") + (e.et.courtLabel ? " (" + e.et.courtLabel + " court)" : "") : ""}
                      {e.courthouse || e.location ? " · " + (e.courthouse || e.location) : ""}
                      {e.outcome_summary ? " — " + e.outcome_summary : ""}
                    </span>
                    {isCovered(e) && (
                      <span style={{ fontSize: 12, color: "var(--text-muted)", display: "block", marginTop: 2 }}>
                        <i data-lucide="radio-tower" style={{ width: 12, height: 12, verticalAlign: "-1px", marginRight: 4 }} />
                        Coverage: <b style={{ color: "var(--text)" }}>{covMethod(e) ? methodShort(covMethod(e)) : "set"}</b>
                        {covDefaults[e.matter_id] && covDefaults[e.matter_id].feed_location ? " · " + covDefaults[e.matter_id].feed_location : ""}
                      </span>
                    )}
                  </div>
                  {e.camera_allowed === true && <span className="cal-flag" title="Cameras allowed"><i data-lucide="video" /></span>}
                  {e.defendant_present === true && <span className="cal-flag" title="Defendant present"><i data-lucide="user-check" /></span>}
                  <button type="button" className="link-btn" title="Change this court date. Ledger keeps it; the nightly Airtable sync won't move it back."
                    onClick={(ev) => { ev.stopPropagation(); moving && moving.e.event_id === e.event_id ? setMoving(null) : startMove(e); }}>
                    <span className="ic-wrap"><i data-lucide="calendar-arrow-up" /></span> Move
                  </button>
                  <i data-lucide="chevron-right" style={{ width: 16, height: 16, color: "var(--lc-slate-300)" }} />
                </li>
                {moving && moving.e.event_id === e.event_id && (
                  <li className="cal-move" onClick={(ev) => ev.stopPropagation()}>
                    <div className="cal-move__fields">
                      <label>{isSpan(e) ? "Trial starts" : "Date"}
                        <input type="date" className="lc-input" value={moving.date} onChange={(ev) => setMoving({ ...moving, date: ev.target.value })} /></label>
                      {isSpan(e) && <label>Trial ends
                        <input type="date" className="lc-input" value={moving.end} onChange={(ev) => setMoving({ ...moving, end: ev.target.value })} /></label>}
                      <label>Time (court’s local)
                        <input type="time" className="lc-input" value={moving.time} onChange={(ev) => setMoving({ ...moving, time: ev.target.value })} /></label>
                      <label className="cal-move__reason">Why (optional)
                        <input type="text" className="lc-input" placeholder="e.g. continued by the judge" value={moving.reason}
                          onChange={(ev) => setMoving({ ...moving, reason: ev.target.value })} /></label>
                    </div>
                    {moving.err && <div className="login__err">{moving.err}</div>}
                    <div className="cal-move__actions">
                      <span className="cal-move__note">Ledger keeps this date from now on. Moving it in Airtable won't change it here.</span>
                      <button type="button" className="link-btn" onClick={() => setMoving(null)} disabled={moving.busy}>Cancel</button>
                      <DS.Button size="sm" variant="primary" icon="check" onClick={saveMove} disabled={moving.busy}>{moving.busy ? "Moving…" : "Move"}</DS.Button>
                    </div>
                  </li>
                )}
                </React.Fragment>
              ))}
            </ul>
          </div>
        )}
      </div>
    );
  }

  // ---- Day view: an Eastern time axis ---------------------------------------
  // Each timed event sits at its EASTERN start time. There is no end time in the
  // data, so every block is drawn as one hour and says so; events whose hours
  // overlap share the row side by side. Placed on the axis only on the day the
  // time belongs to (a multi-day trial's later days have no recorded time), and
  // only when the court's zone is known — otherwise the time is the court's own
  // clock and cannot be placed on an Eastern axis honestly.
  function DayView({ dayISO, events, todayISO, etAbbr, onOpenMatter, isCovered, covMethod, methodShort }) {
    React.useEffect(() => { icons(); });
    // Re-render every minute so the "now" line moves.
    const [, tick] = React.useState(0);
    // The strip is capped: on live data (2026-09-18) 42 of a day's 61 events had no
    // time — mostly later days of multi-day trials — and listing them all pushed the
    // time axis off the first screen. Reset when the day changes.
    const [allUntimed, setAllUntimed] = React.useState(false);
    React.useEffect(() => { setAllUntimed(false); }, [dayISO]);
    React.useEffect(() => { const t = setInterval(() => tick((n) => n + 1), 60000); return () => clearInterval(t); }, []);

    if (events === null) return null;
    const timed = [], untimed = [];
    events.forEach((e) => {
      if (e.et && e.et.known && e.event_date === dayISO) timed.push(e); else untimed.push(e);
    });
    timed.sort((a, b) => a.et.minutes - b.et.minutes);
    // What matters most first: events we are covering, then trials/sentencings/pleas,
    // then things that START today ahead of a trial's later days.
    untimed.sort((a, b) => (isCovered(b) - isCovered(a)) || (isKey(b) - isKey(a))
      || ((a.event_date === dayISO ? 0 : 1) - (b.event_date === dayISO ? 0 : 1))
      || matterName(a).localeCompare(matterName(b)));
    const UNTIMED_CAP = 8;
    const untimedShown = allUntimed ? untimed : untimed.slice(0, UNTIMED_CAP);

    // Visible hours: 7 AM–7 PM ET, widened to fit anything earlier or later.
    const mins = timed.map((e) => Math.min(e.et.minutes, 1439));
    const startH = Math.max(0, Math.min(7, ...mins.map((m) => Math.floor(m / 60))));
    const endH = Math.min(24, Math.max(19, ...mins.map((m) => Math.ceil((m + SLOT_MIN) / 60))));
    const top = (m) => ((m - startH * 60) / 60) * HOUR_PX;

    // Side-by-side columns for overlapping one-hour blocks: group into clusters of
    // mutually overlapping events, then give each event the first free column.
    const placed = [];
    let cluster = [], clusterEnd = -1;
    const flush = () => {
      const cols = [];
      cluster.forEach((p) => {
        let c = 0; while (cols[c] != null && cols[c] > p.m) c++;
        cols[c] = p.m + SLOT_MIN; p.col = c;
      });
      cluster.forEach((p) => { p.cols = cols.length; placed.push(p); });
      cluster = []; clusterEnd = -1;
    };
    timed.forEach((e) => {
      const m = Math.min(e.et.minutes, 1439);
      if (cluster.length && m >= clusterEnd) flush();
      cluster.push({ e, m }); clusterEnd = Math.max(clusterEnd, m + SLOT_MIN);
    });
    if (cluster.length) flush();

    const hours = []; for (let h = startH; h < endH; h++) hours.push(h);
    const isToday = dayISO === todayISO;
    const nowM = window.LedgerTime.nowEasternMinutes();
    const showNow = isToday && nowM >= startH * 60 && nowM <= endH * 60;
    const dayNo = (e) => Math.round((parseISO(dayISO) - parseISO(e.event_date)) / 86400000) + 1;

    const covCls = (e) => (isCovered(e) ? " is-cov" : " is-trk");
    const covWord = (e) => (isCovered(e) ? "Covering" + (covMethod(e) ? " · " + methodShort(covMethod(e)) : "") : "Tracking");

    return (
      <div className="cal-dayview">
        <div className="cal-tzbar">
          <i data-lucide="clock" />
          <span>All times <b>Eastern ({etAbbr})</b>. Courts record times in their own zone; each is converted, and the court’s own time is shown where it differs.</span>
        </div>

        {untimed.length > 0 && (
          <div className="cal-allday">
            <div className="cal-allday-lbl">No set time <span className="cal-allday-n">{untimed.length}</span></div>
            <div className="cal-allday-list">
              {untimedShown.map((e) => (
                <button key={e.event_id} type="button" className={"cal-achip cal-ev--" + category(e) + covCls(e)}
                  title={covWord(e) + " · " + matterName(e) + (e.et && !e.et.known ? " · " + e.et.note : "")}
                  onClick={() => onOpenMatter && onOpenMatter(e.matter_id)}>
                  <b>{matterName(e)}</b>
                  <span>
                    {CAT_META[category(e)].label}
                    {endOf(e) !== e.event_date ? ` · day ${dayNo(e)}` : ""}
                    {e.et && !e.et.known ? ` · ${e.et.short} court time, zone unknown` : ""}
                  </span>
                </button>
              ))}
              {untimed.length > UNTIMED_CAP && (
                <button type="button" className="link-btn cal-allday-more" onClick={() => setAllUntimed((v) => !v)}>
                  {allUntimed ? "Show fewer" : `Show all ${untimed.length}`}
                </button>
              )}
            </div>
          </div>
        )}

        {timed.length === 0 && untimed.length === 0 && (
          <div className="ledger-empty"><i data-lucide="calendar-x" /><b>Nothing in court</b>
            <span>No court events on this day{/* filters are named by the checkboxes above */}.</span></div>
        )}

        {timed.length > 0 && (
          <div className="cal-tl" style={{ height: (endH - startH) * HOUR_PX }}>
            {hours.map((h) => (
              <div key={h} className="cal-tl-hour" style={{ top: (h - startH) * HOUR_PX }}>
                <span className="cal-tl-lbl">{window.LedgerTime.clock(h * 60).replace(":00", "")}</span>
              </div>
            ))}
            {showNow && (
              <div className="cal-tl-now" style={{ top: top(nowM) }} title={"Now · " + window.LedgerTime.clock(nowM) + " " + etAbbr} />
            )}
            <div className="cal-tl-track">
              {placed.map(({ e, m, col, cols }) => (
                <button key={e.event_id} type="button"
                  className={"cal-dcard cal-ev--" + category(e) + covCls(e)}
                  style={{ top: top(m) + 1, height: (SLOT_MIN / 60) * HOUR_PX - 3,
                           left: `calc(${(col / cols) * 100}% + 2px)`, width: `calc(${100 / cols}% - 4px)` }}
                  title={[matterName(e), covWord(e), CAT_META[category(e)].label, etTitle(e.et),
                          e.courthouse || e.location, "No end time recorded; drawn as one hour"].filter(Boolean).join("\n")}
                  onClick={() => onOpenMatter && onOpenMatter(e.matter_id)}>
                  <span className="cal-dcard-time">
                      <b>{e.et.short}</b>
                    {e.et.courtLabel && <span className="cal-dcard-court">{e.et.courtLabel} court</span>}
                  </span>
                  <span className="cal-dcard-name">{matterName(e)}</span>
                  <span className="cal-dcard-sub">{CAT_META[category(e)].label}{e.courthouse ? " · " + e.courthouse : ""}</span>
                </button>
              ))}
            </div>
          </div>
        )}
      </div>
    );
  }

  // ---- Awaiting-court-date watchlist ---------------------------------------
  function AwaitingTab({ onOpenMatter }) {
    const [rows, setRows] = React.useState(null);
    const [total, setTotal] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const LIMIT = 200;
    React.useEffect(() => {
      // ~1,400 matters qualify now, so the list is capped and the true count is
      // read separately — never silently truncated.
      window.LedgerAPI.listAwaitingCourtDate({ limit: LIMIT })
        .then(setRows).catch((e) => setErr(e.message || String(e)));
      window.LedgerAPI.awaitingCourtDateCount().then(setTotal).catch(() => {});
    }, []);
    React.useEffect(() => { icons(); }, [rows]);

    return (
      <div>
        <div className="cal-await-note">
          <i data-lucide="alarm-clock" />
          <div>
            <b>No court date set yet</b> — tracked matters with nothing on the calendar. Keep checking the court
            calendar; when a date lands, add it under the matter’s <b>Case events</b> tab and it moves onto the grid.
            <span className="cal-auto"><i data-lucide="sparkles" /> Automation candidate — watch dockets and flag new dates.</span>
          </div>
        </div>
        {err && <div className="login__err" style={{ marginBottom: 14 }}>{err}</div>}
        {rows && total != null && total > rows.length && (
          <div className="pager pager--note">
            <span className="pager__count">
              Showing the first {rows.length.toLocaleString()} of {total.toLocaleString()} — narrow by state or status on the Matters list to work through the rest.
            </span>
          </div>
        )}
        {rows === null && <div style={{ color: "var(--text-muted)", fontSize: 13 }}>Loading…</div>}
        {rows && rows.length === 0 && (
          <div className="ledger-empty"><i data-lucide="calendar-check" /><b>Nothing waiting</b>
            <span>Every tracked matter has a court date on the calendar.</span></div>
        )}
        {rows && rows.length > 0 && (
          <DS.Card><DS.Card.Body style={{ padding: 0 }}>
            <table className="tbl">
              <thead><tr><th>Matter</th><th>Legal status</th><th>Tracking</th><th>State</th><th>Next action</th><th>Re-check</th></tr></thead>
              <tbody>
                {rows.map((m) => (
                  <tr key={m.matter_id} onClick={() => onOpenMatter && onOpenMatter(m.matter_id)}>
                    <td className="cname">{m.working_title}{m.matter_summary && <span className="row-sub">{m.matter_summary}</span>}</td>
                    <td className="cdef">{m.legal_status ? T(m.legal_status) : "—"}</td>
                    <td className="cdef">{m.coverage_status ? window.vocabLabel("coverage_status", m.coverage_status) : "—"}</td>
                    <td className="cdef">{m.crime_location_state || "—"}</td>
                    <td className="cdef">{m.next_action || "—"}</td>
                    <td className="cdef">{m.check_back_interval ? T(m.check_back_interval) : "—"}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </DS.Card.Body></DS.Card>
        )}
      </div>
    );
  }

  function TrialCalendar({ onOpenMatter }) {
    const { Tabs } = DS;
    const [tab, setTab] = React.useState("calendar");
    const [awaitingCount, setAwaitingCount] = React.useState(null);
    React.useEffect(() => {
      window.LedgerAPI.listAwaitingCourtDate().then((r) => setAwaitingCount(r.length)).catch(() => {});
    }, []);
    React.useEffect(() => { icons(); }, [tab]);

    const tabs = [
      { id: "calendar", label: "Calendar" },
      { id: "awaiting", label: "Awaiting court date", count: awaitingCount == null ? undefined : awaitingCount },
    ];

    return (
      <div>
        <div className="page-head">
          <div>
            <div className="eyebrow">Content Center · Coverage</div>
            <h1 className="page-title">Trial Calendar<window.LedgerComment section="Trial Calendar" /></h1>
          </div>
        </div>
        <div style={{ marginBottom: 16 }}><Tabs items={tabs} value={tab} onChange={setTab} /></div>
        {tab === "calendar" ? <CalendarGrid onOpenMatter={onOpenMatter} /> : <AwaitingTab onOpenMatter={onOpenMatter} />}
      </div>
    );
  }

  window.LedgerTrialCalendar = TrialCalendar;
})();
