// The Viewer's reading room — WP-U14, the half that reads exactly what it was
// shown and nothing else.
//
// THE OTHER HALF OF THE ROOM IS `reading-room.jsx` (2026-08-23): who can read a
// contract, who used to, and the two Legal acts that decide it. It is a
// separate file so the three guards below — three reads, no doorway handler, no
// disabled control — keep describing THIS pane exactly.
//
// ONE ROOM, TWO SIDES, AND THE SECOND SIDE HAD NEVER BEEN BUILT. `0017` created
// cw.agreement_share, granted INSERT and UPDATE on it to both Legal roles,
// wrote the policies, and shipped `POST /shares` and `POST /shares/revoke`
// through the doorway. **No screen ever called either.** So nothing could ever
// enter a viewer's reading room, and the role ADR-0008 created — the audience
// that makes override socialisation mean something — was unusable in principle
// rather than merely empty.
//
// It also granted SELECT on that table to ALL SIX ROLES and served it to
// nobody, so "who can see this agreement, and who used to" had no answer for
// anybody, the Auditor included. `GET /shares` is that answer and is new.
//
// WHAT EACH ROLE GETS, and the differences are AFFORDANCES rather than
// permissions — the database refuses the acts regardless:
//
//   viewer                what was shared with THEM, and the paper. No control
//                         of any kind, which is rule 3 below.
//   legal_reviewer/admin  the whole room, the form that puts a contract in it,
//                         and the control that takes one out. `legal_shares`
//                         and `legal_unshares` name exactly these two roles.
//   requester             the shares on their OWN agreements — "who has been
//                         shown my contract" — and no control. They cannot
//                         share: the point of a socialisation audience is that
//                         somebody else decided who should see it.
//   auditor/administrator every share, live and withdrawn, and no control.
//
// The scoping is the table's own read_scoped policy and the view's own WHERE
// clause. This file passes no parameter and asks for nothing narrower.
//
// WHAT THIS ROLE IS FOR. ADR-0008 created the viewer so a signed contract could
// be shown to somebody — a security lead, a finance partner, a counterparty's
// counsel — without giving them any way to change it. Until 0017 the "shown to"
// half did not exist: nothing recorded who had been shown what, and a viewer
// could read every signed contract in the system. This pane is the surface over
// the mechanism that closed that.
//
// THREE RULES, all from WP-U14, and each is a way this screen could quietly
// become something else:
//
//   1. **NOTHING IS FETCHED BROADER THAN "THIS SHARE, THIS PERSON."** The two
//      endpoints take no parameters at all. There is nothing for this pane to
//      pass, which is the point — the moment the browser can name an agreement,
//      the scoping is a careful query instead of a rule, and a careful query is
//      one edit away from a careless one.
//
//   2. **NO EXPORT. NOT ANYWHERE, NOT EVEN CONVENIENTLY.** ADR-0008 withheld it
//      deliberately: showing somebody a contract and letting them take a copy
//      away are different acts, and only the first was decided. There is no
//      endpoint, nothing in the schema for one to call, and a test on each side
//      that fails if one appears. If a screen ever seems to need it, that is a
//      decision for the owner, not an endpoint.
//
//   3. **NO DISABLED CONTROLS.** A read-only role gets a read-only screen, not a
//      greyed-out editor. Same reasoning as the auditor's workspace: a disabled
//      button says "you could, but not now", and the truth is "this was never
//      yours".
//
// WHAT A VIEWER SEES THAT NOBODY EXPECTS: the approval. cw.reading_room_clause
// carries each clause's reviewer, approval date and origin, because being shown
// a contract is useless if you cannot see whose language it is — that is the
// whole of the socialisation audience's reason for existing.

const { useState } = React;

// ── The origin vocabulary ─────────────────────────────────────────────────
// Keyed to the four values cw.clause_version.origin actually permits. A value
// outside them, or a clause with no origin recorded, falls to _unrecorded and
// is drawn as ABSENT rather than as anything reassuring — an unrecorded origin
// shown as "approved" would be the worst lie this surface could tell.
//
// Each entry carries a SHAPE and a WORD. The colour only reinforces them, so
// the margin still reads for someone who cannot separate the greens from the
// ambers — which is most of the point of drawing it this way.
const ORIGIN = {
  customer_imported: {
    glyph: 'og-external',
    word: 'Customer paper · approved by a person',
  },
  legal_authored: {
    glyph: 'og-legal',
    word: 'Legal wording',
  },
  ai_drafted: {
    glyph: 'og-model',
    word: 'Model drafted · approved by a person',
  },
  vendor_derived: {
    glyph: 'og-vendor',
    word: "From the counterparty's paper",
  },
  external: {
    glyph: 'og-external',
    word: 'External source',
  },
  _unrecorded: {
    glyph: 'og-none',
    word: 'Origin unrecorded',
  },
};

function ReadingRoomPane({ me }) {
  const shares = usePane(() => API.readingRoom());
  const clauses = usePane(() => API.readingRoomClauses());
  const [open, setOpen] = useAddressedRecord('reading-room');
  // ABOVE EVERY EARLY RETURN, and that placement is the whole of the rule.
  // A hook after `if (…) return <Loading />` blanks the pane on the render
  // AFTER the data lands — the one nobody watches (S318).
  const routes = usePane(() => API.noticeRoutes());

  // ONE TAB, TWO SIDES OF THE SAME ROOM, AND TWO FILES. A viewer is being SHOWN
  // things and opens on the paper; everybody else is looking at the room and
  // opens on the register, which lives in `reading-room.jsx` — see that file's
  // header for why it is not in here. The split is by role rather than by
  // response, because the two are different questions rather than the same
  // question answered narrowly.
  //
  // THIS RETURN IS ABOVE EVERY FETCH-DEPENDENT BRANCH AND BELOW EVERY HOOK,
  // which is the only ordering that is correct: a hook after an early return
  // blanks the pane on the render after the data lands (S318), and a role test
  // after `if (shares.status === 'loading')` would make a Legal user wait for a
  // read their half does not use.
  if (me.role !== 'viewer') return <TheRoom me={me} live={shares} />;

  if (shares.status === 'loading') return <Loading />;
  if (shares.status === 'failed') return <LoadFailed reason={shares.reason} />;

  // THE EMPTY ROOM IS A REAL ANSWER AND MUST READ AS ONE. "Nothing has been
  // shared with you" is a true fact about this person's situation, not a
  // failure and not an unbuilt pane. Conflating it with either would be the
  // trap the two empty-state components exist to prevent — and getting it wrong
  // in the alarming direction ("something went wrong") would send somebody
  // chasing a fault that is not there.
  if (shares.rows.length === 0) {
    return (
      <div>
        <PaneHead
          title="Reading room"
          sub="The agreements somebody has shown you, and why." />
        <Empty
          kicker="reading room"
          line="Nothing has been shared with you."
          sub="When Legal shares a signed agreement with you it appears here, with
               the reason it was shared. There is nothing to ask for and nothing
               to search — you see what you were shown." />
      </div>
    );
  }

  // WHERE A SHARED AGREEMENT STANDS, and the reason this is not just
  // `executed_on ? 'signed' : 'agreement'` — which is what it was.
  //
  // `executed_on` answers "was this ever signed?". The person reading this
  // pane is asking "can I rely on it?", and those stopped being the same
  // question the day the term ended. On 2026-08-22 all twelve shares in the
  // demonstration database carried `term_end: 2026-07-21` — every one of them
  // finished a month earlier — and every one was marked SIGNED. Three were
  // also terminated outright.
  //
  // WHY IT MATTERS HERE MORE THAN ANYWHERE ELSE. Everywhere else "signed" is
  // read by somebody who holds the deal and knows its history. ADR-0008 built
  // this pane for a person who has been SHOWN a contract and given no export
  // and no search — this list is the whole of what they get, so a mark that
  // misleads here has nothing to correct it.
  //
  // Compared as plain YYYY-MM-DD strings, which is what the record stores and
  // what the endpoint returns. Turning them into Date objects would introduce
  // a timezone where the record has none, and a contract's term does not end
  // at a different moment depending on who is reading.
  const today = new Date().toISOString().slice(0, 10);
  const standingOf = (s) => {
    if (!s.executed_on) return { word: 'agreement', mark: 'chip-std' };
    if (s.term_end && String(s.term_end).slice(0, 10) < today)
      return { word: 'term ended', mark: 'chip-gone' };
    return { word: 'in force', mark: 'chip-ok' };
  };

  const current = open
    ? shares.rows.find((s) => s.agreement_id === open) : null;

  // THE VIEWER'S HALF OF THE OWNER DECISION (Mike, 2026-08-22: "give everyone
  // the power to send and receive messages"). 0064 shut the viewer out of the
  // message record in writing; 0098 reverses it on his instruction, and this
  // is the control that makes the reversal something a person can do rather
  // than a row in a permissions table.
  //
  // IT SITS BESIDE THE PAPER, NEVER ON THE LIST. Every other entry point keeps
  // the same rule: a notice cites a reference read off the row it is next to,
  // so the citation cannot be mistyped and cannot be invented. The reference
  // here is the agreement this reader currently has open.
  //
  // NOTHING IS OFFERED IF NOTHING WOULD LAND. RaiseNotice draws itself only
  // when a route exists from this role for this kind of subject, so on a
  // system whose routes were never seeded this is simply absent — never a
  // disabled button, which is a promise the system will not keep.
  const ask = current && routes.status === 'loaded' ? (
    <RaiseNotice
      me={me} routes={routes.rows}
      subject={{ kind: 'agreement', ref: current.agreement_id,
                 about: `${current.counterparty} · ${current.agreement_id}` }} />
  ) : null;

  return (
    <div>
      <PaneHead
        title="Reading room"
        sub="The agreements somebody has shown you, and why." />

      <WaitingList
        order="newest"
        items={shares.rows.map((s) => ({
          key: s.share_id,
          title: `${s.counterparty}${s.agreement_id ? ` · ${s.agreement_id}` : ''}`,
          // WHY, always. A share with no stated purpose is one nobody can
          // review later, and the schema refuses one — so it is always here to
          // show, and showing it is what makes "who showed me this, and what
          // for?" answerable by the person being shown.
          // The term goes in the row, not behind a click. A reader deciding
          // whether to rely on a contract should not have to open it to find
          // out that it finished last month.
          sub: `shared by ${s.shared_by} — ${s.purpose}`
             + (s.effective_on || s.term_end
                 ? ` · ${s.effective_on ?? '—'} to ${s.term_end ?? 'no end recorded'}`
                 : ''),
          at: s.shared_at,
          chips: (() => {
            const st = standingOf(s);
            return <span className={`chip ${st.mark}`}>{st.word}</span>;
          })(),
        }))}
        onOpen={(it) => {
          const row = shares.rows.find((s) => s.share_id === it.key);
          setOpen(row && row.agreement_id === open ? null : (row && row.agreement_id));
        }}
        empty={null}
      />

      {current && (
        <div className="mt-6">
          <PanelHead
            title={`${current.counterparty} — the paper`}
            sub={`Effective ${current.effective_on ?? '—'}`
               + `${current.term_end ? `, through ${current.term_end}` : ''}.`}
            right={ask} />

          {clauses.status === 'loading' && <Loading />}
          {clauses.status === 'failed' && <LoadFailed reason={clauses.reason} />}
          {clauses.status === 'loaded' && (() => {
            // Filtering rows the POLICY already returned. cw.reading_room_clause
            // is scoped to this person's shares in its own WHERE clause, so this
            // narrows a list that was already narrow — it does not hide anything
            // that arrived and should not have.
            const body = clauses.rows.filter((c) => c.agreement_id === current.agreement_id);
            if (body.length === 0) {
              return <Empty
                kicker="the paper"
                line="This agreement records no clause-by-clause decisions."
                sub="It was signed, but the run behind it did not record per-clause
                     choices — so there is nothing to show here rather than
                     something missing." />;
            }
            return (
              <div className="sheet">
                {body.map((c, i) => {
                  const o = ORIGIN[c.origin] || ORIGIN._unrecorded;
                  return (
                    <div className="clause" key={`${c.clause_id}@${c.version}`}>
                      <div className="clause-n">{i + 1}</div>
                      <div className="clause-text">
                        <h4>{c.title}</h4>
                        <p style={{ whiteSpace: 'pre-wrap' }}>{c.body}</p>
                      </div>
                      {/* THE ORIGIN MARGIN. Being shown a contract is useless if
                          you cannot see whose language it is. The origin is read
                          from cw.reading_room_clause — the mark is the recorded
                          fact drawn, never a claim this screen makes. The shape
                          carries the meaning and the word states it, so the
                          margin still reads with the colour taken away. */}
                      <div className="clause-margin">
                        <div className={`og ${o.glyph}`} />
                        <span className="og-word">{o.word}</span>
                        <div className="og-detail">
                          {c.reviewer
                            ? `approved by ${c.reviewer}${c.approved_on ? ` on ${c.approved_on}` : ''}`
                            : 'no approver recorded'}
                        </div>
                        <div className="og-ref">{c.clause_id}@v{c.version}</div>
                        {c.provenance ? <div className="og-detail">{c.provenance}</div> : null}
                      </div>
                    </div>
                  );
                })}
              </div>
            );
          })()}
        </div>
      )}

      <p className="caption mt-3">
        You see what was shared with you, and the sharing is recorded — who
        showed you this, when, and why. <strong>There is nothing to export</strong>:
        being shown a contract and taking a copy away are different acts, and
        only the first one was decided. You can raise a question about anything
        you have been shown, and it lands on the recipient's waiting list with
        your name on it — it blocks nothing and approves nothing.
      </p>
    </div>
  );
}
