// Whether the expert panel is actually working — ADR-0013's own three numbers.
//
// WHAT WAS MISSING. `0092` built three report views and `0093` a fourth, every
// one of them tested, and not one had a screen. Handoff 10 calls this "the
// largest single piece of unfinished work in the arc", and PRODUCT.md §4 ranks
// it third of nine gaps: *"Load, waivers and against-advice are the numbers
// ADR-0013 says tell you whether the workflow is real; they exist and are
// unseen."*
//
// ── WHAT EACH ONE IS FOR, because they are easy to confuse ────────────────
//
//   THE LOAD says whether the panel is staffed. A discipline that has never
//   been consulted appears with zeros rather than vanishing — the view is a
//   LEFT JOIN from cw.discipline for exactly that reason, and "an absence you
//   cannot see is the one nobody staffs".
//
//   THE WAIVER FIGURE is the one ADR-0013 calls the number that says whether
//   this is working or is an obstacle. It counts REQUIRED consultations only:
//   an advisory consultation gates nothing, so declining to wait for one
//   waives nothing, and counting those would inflate the figure with acts that
//   carry no weight.
//
//   DECIDED AGAINST ADVICE IS NOT AN ERROR LIST and this screen says so in
//   those words. The record is explicit that an expert's opinion gates nothing
//   and Legal decides; the control is that the disagreement stays VISIBLE. One
//   row per disagreement rather than a count, because a count of these is
//   exactly the performance score it must never become.
//
//   HELD FOR AN EXPERT is two waits, not one, and the view says which: a
//   ticket whose discipline has been ASKED is waiting on the expert, and one
//   that has not is waiting on Legal to ask. Different desks owe them.
//
// ── AND NO SCREEN HERE COMPUTES A RATE ────────────────────────────────────
//
// Every proportion is drawn as a BAR over its own measured total and written
// as `N of M`. A percentage would read the same at four consultations as at
// four hundred, and the first is noise. The geometry is proportional to the
// sample by construction; the words carry the denominator. This is the same
// rule as "derive the third answer, never infer it from two", applied to the
// one place a reporting screen is most tempted to break it.

const { useMemo: useMeasureMemo } = React;

// The three states a consultation ends in, and the ink each already wears
// everywhere else in this product. Written once here rather than at the four
// call sites below, because a vocabulary written out by hand drifts (S325).
const CONSULTATION_BANDS = [
  { key: 'answered', label: 'answered', tone: 'ok' },
  { key: 'waived', label: 'waived', tone: 'warn' },
  { key: 'still_open', label: 'still open', tone: 'wait' },
];

// ── The right-hand column of every measured row ───────────────────────────
//
// `shrink-0 text-right` IS WHAT CANNOT FIT, and it was written here three
// times before the width sweep found it. At 375px the aside on a
// decided-against-advice row held an email address, "decided by <name>" and a
// date; unable to shrink, it took the line and squeezed the quoted opinion
// beside it to THIRTY-SIX PIXELS — measured, scrollWidth 99 against
// clientWidth 36. The same defect as the obligation calendar one day earlier,
// in a file written after the trap for it was recorded.
//
// `.measure-aside` in registry.css keeps the right alignment at a comfortable
// width and lets the column fall UNDER the row when there is not room, which
// is the shape that holds an arbitrarily long value at every width.
// BandBar MOVED TO common.jsx on 2026-08-23, when the retained-language
// figure became its second user. Copying it here would have been three lines
// of work and the start of exactly the drift common.jsx exists to prevent.


// ── 1 · The load ──────────────────────────────────────────────────────────
function ConsultationLoad({ pane }) {
  const rows = pane.rows || [];
  const filter = useListFilter(rows, {
    view: 'panel:load', fields: ['discipline', 'discipline_key'],
  });

  // COUNTED OFF THE ROWS THIS ROLE WAS ANSWERED, never off a paragraph.
  const staffedGap = rows.filter((r) => Number(r.asked) === 0).length;
  const openTotal = rows.reduce((n, r) => n + Number(r.still_open || 0), 0);

  return (
    <div>
      <PanelHead
        title="How loaded each discipline is"
        sub="Every discipline the library defines, including the ones nobody has ever consulted — a discipline that vanishes when it is idle is the one nobody notices is unstaffed."
        right={<FilterCount filter={filter} />} />
      <ListFilter filter={filter} testid="panel-load"
                  placeholder="search by discipline" />

      {rows.length === 0 ? (
        <Empty
          kicker="no disciplines"
          line="Your library defines no expert disciplines."
          sub="Referring a ticket to a discipline is impossible until one exists,
               so this is a fact about the library rather than about the panel.
               Disciplines are Legal's to define." />
      ) : filter.shown.length === 0 ? (
        <NoMatch kicker="no match" noun="disciplines" />
      ) : (
        <>
          <div className="caption mb-3" data-testid="load-summary">
            {/* THE TWO FACTS A READER WANTS BEFORE THE ROWS. Both counted from
                the rows above, so the sentence and the list cannot disagree. */}
            {staffedGap} of {rows.length}{' '}
            {staffedGap === 1 ? 'discipline has' : 'disciplines have'} never been
            consulted · {openTotal} consultation{openTotal === 1 ? '' : 's'} still
            open across all of them.
          </div>
          <div className="panel">
            {filter.shown.map((r) => (
              <div className="waiting-row measure-row" key={r.discipline_key}>
                <div className="min-w-0" style={{ flex: 1 }}>
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className="text-[13px]" style={{ color: 'var(--ink)' }}>
                      {r.discipline}
                    </span>
                    <span className="caption font-mono">{r.discipline_key}</span>
                    {/* REQUIRED IS THE HALF THAT GATES. A discipline whose
                        consultations are all advisory is a different thing from
                        one the rules make compulsory, and the load figure alone
                        cannot tell them apart. */}
                    {Number(r.required) > 0 && (
                      <span className="chip chip-std">
                        {r.required} required
                      </span>
                    )}
                  </div>
                  <div className="mt-2" style={{ maxWidth: 520 }}>
                    <BandBar
                      total={Number(r.asked)}
                      empty="Nothing has ever been referred to this discipline."
                      bands={CONSULTATION_BANDS.map((b) => ({
                        ...b, n: Number(r[b.key] || 0) }))} />
                  </div>
                </div>
                <div className="measure-aside">
                  {/* AN ABSENCE WITH ITS REASON, never a zero. `waiting_since`
                      is null when nothing is open, and `mean_hours_to_answer`
                      is null when nothing has been answered — drawing either as
                      0 would state a measurement nobody made. */}
                  <div className="caption">
                    {r.mean_hours_to_answer === null || r.mean_hours_to_answer === undefined
                      ? 'nothing answered yet'
                      : `${r.mean_hours_to_answer} hours to answer, on average`}
                  </div>
                  <div className="caption mt-1">
                    {r.waiting_since
                      ? <>oldest open wait {since(r.waiting_since)}</>
                      : 'nothing open'}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// ── 2 · The waiver figure ─────────────────────────────────────────────────
function WaiverUse({ pane }) {
  const rows = pane.rows || [];
  const filter = useListFilter(rows, {
    view: 'panel:waivers', fields: ['discipline', 'discipline_key'],
  });
  const waived = rows.reduce((n, r) => n + Number(r.waived || 0), 0);
  const required = rows.reduce((n, r) => n + Number(r.required_consultations || 0), 0);

  return (
    <div>
      <PanelHead
        title="How often a required consultation was waived"
        sub="Required consultations only. An advisory one gates nothing, so declining to wait for it waives nothing — counting those would inflate the figure with acts that carry no weight."
        right={<FilterCount filter={filter} />} />

      {/* THE SENTENCE THAT STOPS THIS BECOMING A SCORE, and it is load-bearing.
          Every gate in this product has a door that is not a lie; the waiver IS
          that door, and how often it is used measures the WORKFLOW. A screen
          that let this read as a league table of lawyers would teach people to
          route around the record rather than use the door — which is the exact
          failure the door exists to prevent. */}
      <div className="panel-2 p-3 mt-2" data-testid="waiver-caveat">
        <div className="tag">what this number is, and is not</div>
        <div className="text-[12.5px] mt-1.5"
             style={{ color: 'var(--mute)', lineHeight: 1.7 }}>
          A waiver is a legitimate door, not a fault. It is what stops people
          routing around the system when an expert cannot answer in time, and
          the reason is recorded every time. A high count is a <strong>prompt to
          look</strong> and never a verdict on anybody: it may equally mean the
          panel has nobody seated, or that the rules requiring consultation are
          drawn too widely. Both of those are Legal's to change, and neither is
          the fault of whoever pressed the button.
        </div>
      </div>

      <ListFilter filter={filter} testid="panel-waivers"
                  placeholder="search by discipline" />

      {rows.length === 0 ? (
        <Empty
          kicker="none required"
          line="No consultation has ever been required."
          sub="Every referral so far has been advisory, or none has been made at
               all. This figure counts required consultations only, so it has
               nothing to count rather than counting nothing." />
      ) : filter.shown.length === 0 ? (
        <NoMatch kicker="no match" noun="disciplines" />
      ) : (
        <>
          <div className="caption mb-3" data-testid="waiver-summary">
            {waived} of {required} required consultation
            {required === 1 ? '' : 's'} across every discipline
            {waived === 0
              ? ' — none has been waived.'
              : ' were waived rather than answered.'}
          </div>
          <div className="panel">
            {filter.shown.map((r) => (
              <div className="waiting-row measure-row" key={r.discipline_key}>
                <div className="min-w-0" style={{ flex: 1 }}>
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className="text-[13px]" style={{ color: 'var(--ink)' }}>
                      {r.discipline}
                    </span>
                    <span className="caption font-mono">{r.discipline_key}</span>
                  </div>
                  <div className="mt-2" style={{ maxWidth: 520 }}>
                    <BandBar
                      total={Number(r.required_consultations)}
                      empty="Nothing required of this discipline."
                      bands={CONSULTATION_BANDS.map((b) => ({
                        ...b, n: Number(r[b.key] || 0) }))} />
                  </div>
                </div>
                <div className="measure-aside">
                  {/* WHO IS DOING IT, so the number leads somewhere. A count
                      with no route to the underlying acts is a number people
                      argue about rather than look into — the view's own words,
                      and this is where that promise is kept or broken. */}
                  <div className="caption">
                    {Number(r.waived) === 0
                      ? 'nobody has waived one'
                      : `${r.waived_by_people} ${Number(r.waived_by_people) === 1
                          ? 'person has' : 'people have'} waived one`}
                  </div>
                  {r.latest_waiver && (
                    <div className="caption mt-1">
                      most recent {since(r.latest_waiver)}
                      {r.first_waiver && r.first_waiver !== r.latest_waiver
                        && <> · first {since(r.first_waiver)}</>}
                    </div>
                  )}
                </div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// ── 3 · Where Legal decided against the advice ────────────────────────────
function DecidedAgainstAdvice({ pane }) {
  const rows = useMeasureMemo(
    () => (pane.rows || []).map((r) => ({
      ...r,
      // FACETED ON WHAT SOMEBODY WOULD SAY, not on the column's value. `unsound`
      // and `qualified` are the record's words and they are the right ones here
      // — but the difference between them is the whole reason there are three
      // advice values and not two, so the facet keeps them apart.
      advice_label: r.advice === 'qualified' ? 'sound, with a proviso' : 'unsound',
    })),
    [pane.rows]);
  const filter = useListFilter(rows, {
    view: 'panel:against-advice',
    fields: ['ticket_id', 'agreement_id', 'discipline', 'category_key',
             'expert', 'decided_by', 'reasoning'],
    facet: 'advice_label',
  });

  return (
    <div>
      <PanelHead
        title="Where Legal decided against the advice"
        sub="One row per disagreement, never a count — a count of these is the performance score this must not become."
        right={<FilterCount filter={filter} />} />

      {/* SAID BEFORE THE ROWS, because a list with this title reads as a naughty
          list and nothing else on the screen would correct that impression. */}
      <div className="panel-2 p-3 mt-2" data-testid="advice-caveat">
        <div className="tag">this is not an error list</div>
        <div className="text-[12.5px] mt-1.5"
             style={{ color: 'var(--mute)', lineHeight: 1.7 }}>
          An expert gives an opinion; it gates nothing, and Legal decides. That
          is the design, not a loophole — the control is that the disagreement
          stays <strong>visible</strong>, and this list is what makes "visible"
          true at more than one row at a time. A row here is a decision somebody
          made with the advice in front of them, which is exactly what the
          workflow is for.
        </div>
      </div>

      <ListFilter filter={filter} testid="panel-against-advice"
                  placeholder="ticket, agreement, discipline, expert, or who decided"
                  facetLabel="either kind of advice" />

      {rows.length === 0 ? (
        <Empty
          kicker="no disagreements"
          line="No ticket has been verified against what the expert said."
          sub="Either every answered consultation agreed, or none of the tickets
               carrying one has been decided yet. A clean list here is a real
               answer and not an empty screen." />
      ) : filter.shown.length === 0 ? (
        <NoMatch kicker="no match" noun="disagreements" />
      ) : (
        <div className="panel">
          {/* THE ROW NAMES THE TICKET AND THE AGREEMENT AND DOES NOT OPEN THEM.
              There is no addressed ticket record anywhere in this application,
              and this pane's audience includes the Auditor, who holds no
              tickets tab at all — so a pressable row would lead nowhere for at
              least one of the two roles that can read the screen. Naming both
              is what lets somebody go and look, which is the whole purpose the
              view was written for; an affordance that led nowhere would teach
              them to stop pressing.

              AND THIS COMMENT SITS BESIDE THE `.map`, NOT INSIDE IT. Written
              above the element an arrow function returns, a JSX comment is a
              SECOND ROOT and the file stops parsing — which blanks every
              component in it. Made twice in two days; caught this time by
              re-parsing after the edit rather than before.

              AND NOTE WHAT THIS SENTENCE NO LONGER CONTAINS. It used to spell
              the comment braces out literally, which put a block-comment
              terminator inside a block comment; comments do not nest, so the
              comment ended there and the file stopped parsing — the warning
              caused the very fault it warns about, and sat broken on main for
              weeks. Describe the construct in words. */}
          {filter.shown.map((r) => (
            <div className="waiting-row measure-row"
                 key={`${r.ticket_id}-${r.discipline_key}`}>
              <div className="min-w-0" style={{ flex: 1 }}>
                <div className="flex items-center gap-2 flex-wrap">
                  <span className="font-mono text-[12.5px]">ticket {r.ticket_id}</span>
                  {/* WHICH AGREEMENT — the thing Legal triages by, and the
                      column a queue was once missing entirely. */}
                  <span className="font-mono caption">{r.agreement_id || 'no agreement'}</span>
                  <span className="caption">{r.discipline}</span>
                  <span className={`chip ${r.severity === 'High' ? 'chip-pending' : 'chip-std'}`}>
                    {r.severity}
                  </span>
                  <Status state={r.advice === 'qualified' ? 'pending' : 'never'}>
                    {r.advice_label}
                  </Status>
                  {r.necessity === 'required' && (
                    <span className="chip chip-std">was required</span>
                  )}
                </div>
                {/* THE EXPERT'S OWN SENTENCE, set plainly. It is a person
                    speaking, so it takes the quotation idiom this product uses
                    for exactly that — and a machine's words never would. */}
                {r.reasoning && (
                  <div className="panel-2 p-3 mt-2 relative">
                    <span className="font-serif" style={{
                      position: 'absolute', left: 6, top: -6, fontSize: 34,
                      color: 'var(--accent)', opacity: .55, lineHeight: 1 }}
                      aria-hidden="true">“</span>
                    <div className="font-serif italic"
                         style={{ fontSize: 14, lineHeight: 1.6, paddingLeft: 22 }}>
                      {r.reasoning}
                    </div>
                  </div>
                )}
              </div>
              <div className="measure-aside">
                <div className="caption font-mono">{r.expert}</div>
                <div className="caption mt-1">
                  decided by <span className="font-mono">{r.decided_by}</span>
                </div>
                {r.decided_on && (
                  <div className="caption mt-1">{since(r.decided_on)}</div>
                )}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ── 4 · Work that has stopped, waiting for an expert ──────────────────────
//
// TWO WAITS, AND THE VIEW SAYS WHICH. A ticket whose discipline has been ASKED
// is waiting on the expert; one that has not is waiting on Legal to ask. They
// are owed by different desks and a screen that merged them would send every
// chase to the wrong person.
function HeldForAnExpert({ pane }) {
  const rows = useMeasureMemo(
    () => (pane.rows || []).map((r) => ({
      ...r,
      waiting_on: r.asked ? 'the expert' : 'Legal to ask',
    })),
    [pane.rows]);
  const filter = useListFilter(rows, {
    view: 'panel:held',
    fields: ['ticket_id', 'agreement_id', 'category_key', 'discipline', 'opened_by'],
    facet: 'waiting_on',
  });
  const unasked = rows.filter((r) => !r.asked).length;

  return (
    <div>
      <PanelHead
        title="Work that has stopped, waiting for an expert"
        sub="Pending tickets with a consultation the rules still require. Ordered by how long the ticket has waited, because that is what the person who raised it is feeling."
        right={<FilterCount filter={filter} />} />
      <ListFilter filter={filter} testid="panel-held"
                  placeholder="ticket, agreement, category, discipline, or who raised it"
                  facetLabel="either wait" />

      {rows.length === 0 ? (
        <Empty
          kicker="nothing held"
          line="No pending ticket is waiting on an expert."
          sub="Either nothing needs a discipline the rules require, or every
               required consultation has been answered or waived." />
      ) : filter.shown.length === 0 ? (
        <NoMatch kicker="no match" noun="tickets" />
      ) : (
        <>
          {unasked > 0 && (
            <div className="caption mb-3" data-testid="held-unasked">
              {/* THE HALF LEGAL OWES, said separately because it is the half
                  somebody on this screen can actually do something about. */}
              {unasked} of {rows.length}{' '}
              {unasked === 1 ? 'is waiting for the question to be ASKED'
                : 'are waiting for the question to be ASKED'} — no consultation
              has been opened yet, and that wait is Legal's rather than the
              expert's.
            </div>
          )}
          <div className="panel">
            <table className="ledger w-full">
              <thead>
                <tr>
                  <th>ticket</th><th>agreement</th><th>category</th>
                  <th>discipline</th><th>waiting on</th><th>waiting</th>
                </tr>
              </thead>
              <tbody>
                {filter.shown.map((r) => (
                  <tr key={`${r.ticket_id}-${r.discipline_key}`}>
                    <td className="font-mono">{r.ticket_id}</td>
                    <td className="font-mono">{r.agreement_id || '—'}</td>
                    <td>{r.category_key}</td>
                    <td>{r.discipline}</td>
                    <td>
                      {r.asked
                        ? <Status state="pending" title={`asked ${r.asked_at || ''}`}>
                            the expert
                          </Status>
                        : <Status state="never">Legal to ask</Status>}
                    </td>
                    <td className="caption">
                      {r.waiting_days} {Number(r.waiting_days) === 1 ? 'day' : 'days'}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </>
      )}
    </div>
  );
}

// ── The whole measurement board ───────────────────────────────────────────
//
// EACH SECTION RENDERS ON ITS OWN READ. The three `0092` reports are granted to
// the Legal admin and the Auditor alone; `held_for_an_expert` scopes itself and
// answers more roles. A single early return on the first refusal would take
// three working sections away because a fourth was not this caller's to see —
// and a section drawn EMPTY on a refusal would be worse still, because "nothing
// is held" and "I may not read what is held" are different sentences and only
// one of them is true.
function PanelMeasures({ load, waivers, against, held }) {
  const sections = [
    { key: 'load', pane: load, what: 'the panel\'s load',
      render: () => <ConsultationLoad pane={load} /> },
    { key: 'waivers', pane: waivers, what: 'the waiver figure',
      render: () => <WaiverUse pane={waivers} /> },
    { key: 'against', pane: against, what: 'decisions against advice',
      render: () => <DecidedAgainstAdvice pane={against} /> },
    { key: 'held', pane: held, what: 'work held for an expert',
      render: () => <HeldForAnExpert pane={held} /> },
  ];

  return (
    <div>
      {sections.map((s) => (
        <div className="mt-8" key={s.key} data-testid={`measure-${s.key}`}>
          {s.pane.status === 'loading' ? <Loading />
            : s.pane.status === 'failed'
              ? <div>
                  <PanelHead title={s.what}
                             sub="This measurement was refused, so nothing below it is being claimed." />
                  <LoadFailed reason={s.pane.reason} />
                </div>
              : s.render()}
        </div>
      ))}
    </div>
  );
}
