// ═══════════════════════════════════════════════════════════════════════════
// THE ROOM TEMPLATE
//
// One grid. Every room uses it, so the deck reads as one object rather than
// as nine designs. Extracted from the Problem room, which was the only room
// already doing this correctly.
//
//   ROOM LABEL     top left, 13px uppercase
//   HEADLINE       left column, display serif, two lines maximum
//   SUPPORT        right column, sans 18px, accent rule on its left edge,
//                  one paragraph, 45 words
//   FIGURE ROW     full width beneath both columns, 2-4 figures,
//                  exactly one visibly dominant
//   MEDIA          optional, for the rooms that own a real asset
//   AFTER          optional, the close actions — the Ask room only
//   QUESTIONS      existing pill style, left aligned
//
// Forbidden by construction, not by discipline: there is no slot for a table,
// no slot for a third column, no slot for a second paragraph, and nothing in
// here centres. A room cannot reintroduce those without editing this file.
// ═══════════════════════════════════════════════════════════════════════════

const { useState: useTS, useEffect: useTE } = React;

const isFillText = (s) => /\[FILL/.test(String(s == null ? '' : s));

// A [FILL] marker is a promise that no plausible value was invented in its
// place. It has to be visible as a gap, so it is coral wherever it appears —
// including in the middle of a sentence.
function markFills(node) {
  if (typeof node !== 'string' || !isFillText(node)) return node;
  return node.split(/(\[FILL[^\]]*\])/g).filter(Boolean).map((part, i) =>
    /^\[FILL/.test(part)
      ? <span key={i} style={{ color: 'var(--rt-alert)' }}>{part}</span>
      : <React.Fragment key={i}>{part}</React.Fragment>
  );
}

// ── Colour carries meaning or it is not used. coral = a cost or a gap,
//    gold = AdMel acting or a resolved state, green = a confirmed outcome.
//    A figure with no meaning to signal stays in the body colour.
const FIG_COLOUR = {
  coral: 'var(--rt-alert)',
  gold:  'var(--rt-accent)',
  green: 'var(--rt-ok)',
};

// The row is sized by how many figures stand on it: four to a shelf needs
// smaller type than three, or the widest number runs past its column. The
// dominant stays at roughly 1.6:1 either way, never 3:1.
const FIG_SIZE = {
  3: { base: 'clamp(26px, 2.6vw, 38px)', small: 'clamp(20px, 2vw, 29px)',   dom: 'clamp(40px, 4.2vw, 68px)' },
  4: { base: 'clamp(22px, 2.2vw, 32px)', small: 'clamp(18px, 1.8vw, 26px)', dom: 'clamp(34px, 3.5vw, 51px)' },
};

// A short hero can be set larger without running past its column, and it has
// to be: the hero figure is the largest element in its room, headline
// included. A long one falls back so it still fits four to a shelf.
function figSize(v, cols, dominant, small) {
  const t = FIG_SIZE[cols] || FIG_SIZE[3];
  if (small) return t.small;
  if (!dominant) return t.base;
  const n = String(v).replace(/\s/g, '').length;
  // A one or two character hero is mostly white space at the normal dominant
  // size, so it loses to the wide figures beside it even when it is
  // measurably taller: the eye compares ink, not cap height. Ratios above
  // are set for figures of similar length and do not hold for a single
  // glyph, so a short hero is sized against its neighbours' mass instead.
  if (n <= 2) return cols >= 4 ? 'clamp(48px, 5.4vw, 84px)' : 'clamp(56px, 6vw, 96px)';
  if (cols >= 4 && n <= 6) return 'clamp(40px, 4.6vw, 68px)';
  return t.dom;
}

// ── A shelf, not a rule. The number stands ON the ledge, and the label
//    hangs beneath it as type: a monospace code and one line of plain
//    description. It was a filled card, and four filled cards put nine
//    times more ink on the page than the figures they described, which
//    inverted the hierarchy the room depends on. Nothing here is boxed.
//    The code carries no accent colour, so a room with no gold in it stays
//    that way and the figure is the only coloured thing on the shelf.
function ShelfFigure({ code, v, k, c, dominant, small, fig, cols, peek, ride }) {
  const Fig = window.Fig;
  const fill = isFillText(v) || isFillText(k);
  const num = (
    <span style={{
      fontFamily: 'var(--serif)', fontWeight: 600,
      fontSize: figSize(v, cols, dominant, small),
      lineHeight: 1.0, letterSpacing: '-0.02em',
      fontVariantNumeric: 'tabular-nums',
      color: fill ? 'var(--rt-alert)' : (FIG_COLOUR[c] || 'var(--rt-fg)'),
      display: 'inline-block', whiteSpace: 'nowrap',
      // Leaves the baseline 3px above the ledge at any figure size.
      marginBottom: 'calc(3px - 0.1488em)',
    }}>{markFills(v)}</span>
  );
  return (
    <div className="shelf-fig">
      <div className="shelf-num">{fig ? <Fig id={fig} tone={FIG_COLOUR[c] || 'var(--rt-fg)'}>{num}</Fig> : num}</div>
      <div className="shelf-edge">
        {peek && window.OttoPeek && <window.OttoPeek delay={1600} />}
        {ride && window.OttoRide && <window.OttoRide kind={ride} />}
      </div>
      <div className="shelf-tag">
        <div className="shelf-code">{code}</div>
        <div className="shelf-desc">{markFills(k)}</div>
      </div>
    </div>
  );
}

// One Otto moment per room. A room that gets a crossing does not also get a
// peek: two would read as a cartoon rather than a detail.
const RIDES = { scale: 'boat', problem: 'cart', proof: 'pigeon' };

function TemplateRoom({ tone, label, headline, support, figures, media, after, room, headFig }) {
  const Fig = window.Fig;
  const t = tone === 'light' ? 'light' : 'dark';
  const figs = figures || [];

  // The support rule agrees with the room's dominant figure, so a room made
  // entirely of costs is not accented in the colour that means AdMel acting.
  const dom = figs.find(f => f.dominant);
  const roomAccent = (dom && FIG_COLOUR[dom.c]) || 'var(--rt-accent)';

  // One dominant figure per room is a rule, so it is enforced here rather
  // than left to whoever writes the next room.
  useTE(() => {
    const n = figs.filter(f => f.dominant).length;
    if (n !== 1 && figs.length) {
      console.warn('[template] room "' + room + '" declares ' + n + ' dominant figures; exactly one is required.');
    }
  }, [room, figs.length]);

  const headEl = (
    <h2 style={{
      margin: 0, maxWidth: '22ch',
      fontFamily: 'var(--serif)', fontWeight: 500,
      fontSize: 'clamp(32px, min(4vw, 6vh), 52px)',
      lineHeight: 1.08, letterSpacing: '-0.02em',
      color: 'var(--rt-fg)',
    }}>{headline}</h2>
  );

  return (
    <div className="room room-tpl" data-tone={t} style={{
      position: 'absolute', inset: 0,
      paddingTop: 'calc(56px + var(--s3))', paddingBottom: 'var(--s3)',
      paddingInline: 'var(--margin)',
      display: 'flex', flexDirection: 'column', justifyContent: 'center',
      // The content column is capped at --measure. Pinned to flex-start it
      // hugs the left edge on any viewport wider than the cap, which left the
      // whole deck off-centre on a large monitor. It centres instead; the text
      // inside stays left aligned to the column.
      alignItems: 'center', textAlign: 'left', overflow: 'hidden',
    }}>
      <div style={{ width: '100%', maxWidth: 'var(--measure)' }}>

        <div style={{
          fontFamily: 'var(--sans)', fontSize: 'var(--t-label)', fontWeight: 600,
          letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--rt-label)',
        }}>{label}</div>

        <div className="tpl-cols" style={{ marginTop: 'var(--s3)' }}>
          {headFig ? <Fig id={headFig} tone="var(--rt-accent)">{headEl}</Fig> : headEl}
          <div className="tpl-support" style={{
            fontFamily: 'var(--sans)', fontSize: 18, lineHeight: 1.5,
            color: 'var(--rt-muted)', borderLeftColor: roomAccent,
          }}>{markFills(support)}</div>
        </div>

        {media && <div style={{ marginTop: 'var(--s4)' }}>{media}</div>}

        {figs.length > 0 && (
          <div className="tpl-figs"
               style={{ marginTop: 'var(--s5)',
                        '--shelf-cols': figs.length }}>
            {figs.map((f, i) => (
              <ShelfFigure key={i} {...f} cols={figs.length}
                ride={i === 0 ? RIDES[room] : undefined}
                peek={!RIDES[room] &&
                      i === (window.__ottoPeekIndex ? window.__ottoPeekIndex(room, figs.length) : -1)} />
            ))}
          </div>
        )}

        {after && <div style={{ marginTop: 'var(--s4)' }}>{after}</div>}

        <div style={{ marginTop: 'var(--s2)' }}>
          {room && <window.Objections room={room} align="left" onDark={t === 'dark'} />}
        </div>
      </div>
    </div>
  );
}

window.TemplateRoom = TemplateRoom;
window.__isFill = isFillText;
