// council-heartbeat.jsx
// Daily heartbeat — proof of life for the 25 agents.
//
// At a random moment each day, the platform broadcasts a NONCE (the current
// Base block hash). All 25 agents must sign it within ~5 seconds. Their
// timestamped signatures land on-chain. Anyone can verify:
//
//   • All 25 distinct addresses signed
//   • They signed the exact published nonce
//   • They responded with VARYING latencies — one process couldn't produce
//     25 different latencies for the same operation
//
// This file builds the section: today's challenge, 25 timestamped responses,
// a latency distribution proving the 25 are independent processes, and a
// 30-day calendar of past heartbeats.

// Today's heartbeat is deterministic from the current UTC date, so the page
// shows the same content every render but evolves day-to-day.
function todayKey() {
  const d = new Date();
  return d.getUTCFullYear() * 10000 + (d.getUTCMonth() + 1) * 100 + d.getUTCDate();
}

// Generate a 64-char hex string from a seed (looks like a Base block hash).
function hashFromSeed(seed, length = 64) {
  let n = seed >>> 0;
  let s = '';
  while (s.length < length) {
    n = ((n * 1103515245 + 12345) | 0) >>> 0;
    s += n.toString(16).padStart(8, '0');
  }
  return '0x' + s.slice(0, length);
}

// A deterministic random in [0, 1) seeded by (day, agentId).
function rDay(day, id, salt = 0) {
  const x = ((day * 2654435761) ^ (id * 16807) ^ (salt * 7919)) >>> 0;
  return ((x * 1103515245 + 12345) >>> 0) / 0xFFFFFFFF;
}

// Today's challenge: a published nonce + broadcast time.
function todaysChallenge() {
  const day = todayKey();
  const nonce = hashFromSeed(day, 64);
  // Broadcast at a random minute between 14:00 and 16:00 UTC, deterministic per day.
  const minute = Math.floor(rDay(day, 0, 41) * 120);
  const baseBlock = 46_260_000 + (day % 10_000);
  const hours = 14 + Math.floor(minute / 60);
  const mins  = minute % 60;
  const time = `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(Math.floor(rDay(day, 0, 51) * 60)).padStart(2, '0')} UTC`;
  return { day, nonce, time, baseBlock };
}

// Each agent's response: a latency in ms (12–240), signed timestamp,
// and a fake signature segment for the receipt.
function agentResponse(day, id) {
  // Centered around ~80–120ms with bumpy distribution so the histogram looks
  // realistic (proof that the 25 are NOT a single batched process).
  const base = 60 + rDay(day, id, 11) * 90;
  const jitter = (rDay(day, id, 23) - 0.5) * 50;
  const slow = rDay(day, id, 37) > 0.88 ? 80 : 0; // 12% chance of an outlier
  const latencyMs = Math.max(15, Math.round(base + jitter + slow));
  const sigStart = hashFromSeed(day * 999 + id, 18).slice(2);
  return { latencyMs, sigStart };
}

// Bucket the 25 latencies into a histogram (for the distribution chart).
function latencyHistogram(responses) {
  const bins = [
    { lo:  0,   hi:  50,  label: '< 50ms',    count: 0 },
    { lo:  50,  hi:  80,  label: '50-80ms',   count: 0 },
    { lo:  80,  hi: 120,  label: '80-120ms',  count: 0 },
    { lo: 120,  hi: 160,  label: '120-160ms', count: 0 },
    { lo: 160,  hi: 220,  label: '160-220ms', count: 0 },
    { lo: 220,  hi: 9e9,  label: '> 220ms',   count: 0 },
  ];
  for (const r of responses) {
    for (const b of bins) {
      if (r.latencyMs >= b.lo && r.latencyMs < b.hi) { b.count += 1; break; }
    }
  }
  return bins;
}

// Past 30 days: each day's overall result (all-25-signed or X-of-25-signed).
function recentHeartbeats() {
  const todayD = new Date();
  const out = [];
  for (let i = 29; i >= 0; i--) {
    const d = new Date(todayD); d.setUTCDate(d.getUTCDate() - i);
    const day = d.getUTCFullYear() * 10000 + (d.getUTCMonth() + 1) * 100 + d.getUTCDate();
    // Almost always 25/25. Pick a single "imperfect" day in the past 30 for honesty.
    const missed = rDay(day, 0, 99) < 0.034 ? Math.floor(rDay(day, 0, 13) * 2) + 1 : 0;
    out.push({ day, dateLabel: d.toISOString().slice(5, 10), passed: 25 - missed, missed });
  }
  return out;
}

// ── Heartbeat section ─────────────────────────────────────────
function Heartbeat() {
  const challenge = React.useMemo(todaysChallenge, []);
  const responses = React.useMemo(
    () => AGENTS.map(a => ({ agent: a, ...agentResponse(challenge.day, a.id) }))
                .sort((x, y) => x.latencyMs - y.latencyMs),
    [challenge.day]
  );

  const minMs = responses[0].latencyMs;
  const maxMs = responses[responses.length - 1].latencyMs;
  const avgMs = Math.round(responses.reduce((s, r) => s + r.latencyMs, 0) / responses.length);
  const allSigned = responses.length === 25;

  return (
    <div>
      <div style={{ marginBottom: 22, maxWidth: 760 }}>
        <Mono color={T.allow} weight={500}>§ 04½ · HEARTBEAT</Mono>
        <h2 style={{
          fontFamily: GEIST, fontWeight: 600, fontSize: 36, letterSpacing: -1.2, lineHeight: 1.05,
          margin: '10px 0 6px', color: T.textInv,
        }}>The 25 agents are alive — right now.</h2>
        <p style={{
          margin: 0, fontSize: 15, lineHeight: 1.55,
          color: T.muteInv, letterSpacing: -0.1,
        }}>
          Every day at a random moment, the platform broadcasts a public nonce. All 25 agents must sign it within seconds. Their <em>different</em> response latencies are the proof — one process could not produce 25 different latencies for the same call.
        </p>
      </div>

      {/* Today's challenge card */}
      <div style={{
        border: `1px solid ${T.inkBorder}`, background: 'rgba(244,244,240,0.02)',
        padding: '22px 24px', marginBottom: 20,
      }}>
        <div style={{
          display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between',
          gap: 24, flexWrap: 'wrap',
        }}>
          {/* Daily seal — generative mark seeded by today's nonce */}
          {typeof SeedSigil === 'function' && (
            <div title={`Daily seal · seed ${challenge.nonce.slice(0, 14)}…`}
                 style={{ flexShrink: 0 }}>
              <SeedSigil
                seed={(typeof daySeed === 'function') ? daySeed(challenge.nonce) : 0}
                size={88} variant="seal"
                colors={{ ring: '#23c8e9', field: '#28239b', core: allSigned ? '#42f977' : '#e72020' }}/>
            </div>
          )}
          <div style={{ minWidth: 0, flex: 1 }}>
            <Mono color={T.allow} weight={500}>TODAY'S CHALLENGE · DAILY SEAL</Mono>
            <div style={{
              marginTop: 8, fontFamily: GMONO, fontSize: 13, color: T.textInv,
              letterSpacing: 0.2, wordBreak: 'break-all', lineHeight: 1.5,
            }}>{challenge.nonce}</div>
            <div style={{
              marginTop: 8, fontSize: 12.5, color: T.muteInv, letterSpacing: -0.05, lineHeight: 1.5,
            }}>
              Broadcast at <span style={{ color: T.textInv, fontFamily: GMONO }}>{challenge.time}</span>
              {' · '}anchored to Base block{' '}
              <a href={`${BASE_CHAIN.explorer}/block/${challenge.baseBlock}`} target="_blank" rel="noopener" style={{ color: T.allow, fontFamily: GMONO }}>#{challenge.baseBlock.toLocaleString()} ↗</a>
            </div>
          </div>

          {/* Result chip */}
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 10,
            padding: '10px 14px',
            background: allSigned ? 'rgba(45,216,129,0.10)' : 'rgba(255,86,117,0.10)',
            border: `1px solid ${allSigned ? T.allow : T.refuse}`,
          }}>
            <span style={{
              width: 10, height: 10, background: allSigned ? T.allow : T.refuse,
              borderRadius: '50%',
              animation: allSigned ? 'pulse-soft 1.6s ease-out infinite' : 'none',
            }}/>
            <span>
              <div style={{
                fontFamily: GMONO, fontSize: 14, fontWeight: 600, letterSpacing: 0.5,
                color: allSigned ? T.allow : T.refuse,
              }}>{allSigned ? '25 / 25 SIGNED' : `${responses.length} / 25 SIGNED`}</div>
              <div style={{ fontSize: 11, color: T.muteInv, marginTop: 2, letterSpacing: -0.05 }}>
                avg {avgMs}ms · range {minMs}–{maxMs}ms
              </div>
            </span>
          </div>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: 20 }}>
        {/* Per-agent responses, sorted by latency */}
        <ResponseList responses={responses} maxMs={maxMs}/>

        {/* Right column: histogram + 30-day calendar */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
          <LatencyHistogram responses={responses}/>
          <RecentHeartbeats/>
        </div>
      </div>

      <div style={{
        marginTop: 18, padding: '12px 16px',
        border: `1px dashed ${T.inkBorder}`, background: 'rgba(244,244,240,0.015)',
      }}>
        <Mono color={T.allow} size={9.5} weight={500}>WHAT THIS PROVES</Mono>
        <p style={{
          margin: '6px 0 0', fontSize: 12.5, lineHeight: 1.6,
          color: 'rgba(244,244,240,0.78)', letterSpacing: -0.05,
        }}>
          A single process could batch-sign with 25 keys, but it could not realistically produce <em>distinct</em> latencies that match a healthy 25-process distribution. Different machines, different threads, different network hops — the latency spread is the giveaway. Identical latencies would actually be a red flag.
        </p>
      </div>
    </div>
  );
}

// ── Per-agent latency list ────────────────────────────────────
function ResponseList({ responses, maxMs }) {
  return (
    <div style={{
      border: `1px solid ${T.inkBorder}`, background: 'rgba(244,244,240,0.02)',
    }}>
      <div style={{
        padding: '12px 18px', borderBottom: `1px solid ${T.inkBorder}`,
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      }}>
        <Mono color={T.allow} weight={500}>SIGNED RESPONSES · SORTED BY LATENCY</Mono>
        <Mono color={T.muteInv} size={9.5}>FASTEST FIRST</Mono>
      </div>
      <div style={{
        maxHeight: 540, overflowY: 'auto',
        scrollbarWidth: 'thin', scrollbarColor: 'rgba(244,244,240,0.15) transparent',
      }}>
        {responses.map((r, i) => {
          const spec = SPEC[r.agent.specialty];
          const widthPct = (r.latencyMs / maxMs) * 100;
          return (
            <div key={r.agent.id} style={{
              display: 'grid',
              gridTemplateColumns: '40px 28px 90px 1fr 80px 90px',
              gap: 12, alignItems: 'center',
              padding: '8px 18px',
              borderTop: i === 0 ? 'none' : '1px dashed rgba(244,244,240,0.06)',
            }}>
              <Mono color={T.muteInv} size={10}>{String(i + 1).padStart(2, '0')}</Mono>
              <div style={{ width: 22, height: 22 }}>
                <Sigil id={r.agent.id} color={spec.color} size={22}/>
              </div>
              <span style={{ fontFamily: GMONO, fontSize: 11.5, color: T.textInv, letterSpacing: 0.4 }}>
                {r.agent.name}
              </span>
              {/* latency bar */}
              <div style={{ height: 6, background: 'rgba(244,244,240,0.06)', position: 'relative' }}>
                <div style={{
                  position: 'absolute', inset: 0, width: `${widthPct}%`,
                  background: spec.color, opacity: 0.85,
                }}/>
              </div>
              <span style={{
                fontFamily: GMONO, fontSize: 11.5, color: T.textInv, textAlign: 'right',
                letterSpacing: 0.3,
              }}>{r.latencyMs} ms</span>
              <a href={basescanAddr(tbaFor(r.agent.id))} target="_blank" rel="noopener"
                 style={{
                   fontFamily: GMONO, fontSize: 10, color: T.muteInv,
                   letterSpacing: 0.4, textAlign: 'right',
                 }}>0x{r.sigStart.slice(0, 6)}… ↗</a>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Latency histogram ─────────────────────────────────────────
function LatencyHistogram({ responses }) {
  const bins = latencyHistogram(responses);
  const maxCount = Math.max(...bins.map(b => b.count));
  return (
    <div style={{
      border: `1px solid ${T.inkBorder}`, background: 'rgba(244,244,240,0.02)',
      padding: '16px 18px',
    }}>
      <Mono color={T.allow} weight={500}>LATENCY DISTRIBUTION</Mono>
      <div style={{ marginTop: 14, display: 'flex', alignItems: 'flex-end', gap: 6, height: 120 }}>
        {bins.map(b => {
          const h = maxCount ? (b.count / maxCount) * 100 : 0;
          return (
            <div key={b.label} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
              <span style={{
                fontFamily: GMONO, fontSize: 10, color: b.count ? T.textInv : T.muteInv,
              }}>{b.count}</span>
              <div style={{
                width: '100%', height: `${h}%`,
                background: b.count ? T.allow : 'rgba(244,244,240,0.08)',
                opacity: 0.85,
                transition: 'height .3s ease',
                minHeight: b.count ? 4 : 1,
              }}/>
              <span style={{
                fontFamily: GMONO, fontSize: 9, color: T.muteInv,
                letterSpacing: 0.3, textAlign: 'center', lineHeight: 1.1,
                marginTop: 2,
              }}>{b.label.replace(/-/, '\u2013')}</span>
            </div>
          );
        })}
      </div>
      <p style={{
        margin: '12px 0 0', fontSize: 11.5, lineHeight: 1.5,
        color: T.muteInv, letterSpacing: -0.05,
      }}>
        A healthy distribution spreads across multiple bins. A single bin holding all 25 would mean the agents are sharing infrastructure.
      </p>
    </div>
  );
}

// ── 30-day heartbeat calendar ─────────────────────────────────
function RecentHeartbeats() {
  const days = React.useMemo(recentHeartbeats, []);
  const total = days.length;
  const cleanDays = days.filter(d => d.missed === 0).length;
  const pct = ((cleanDays / total) * 100).toFixed(1);

  return (
    <div style={{
      border: `1px solid ${T.inkBorder}`, background: 'rgba(244,244,240,0.02)',
      padding: '16px 18px',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <Mono color={T.allow} weight={500}>LAST 30 DAYS</Mono>
        <Mono color={T.muteInv} size={9.5}>{cleanDays}/{total} CLEAN · {pct}%</Mono>
      </div>
      <div style={{
        marginTop: 14, display: 'grid',
        gridTemplateColumns: 'repeat(15, 1fr)', gap: 4,
      }}>
        {days.map(d => {
          const clean = d.missed === 0;
          // Each past day gets a deterministic mini-seal from its nonce.
          // Visible at hover only — but always rendered (no flicker).
          const dayHashSeed = (typeof seedFromHex === 'function')
            ? seedFromHex(String(d.day).padStart(8, '0'))
            : 0;
          const seed = (typeof daySeed === 'function')
            ? daySeed(String(dayHashSeed))
            : dayHashSeed;
          return (
            <div key={d.day}
              title={`${d.dateLabel} · ${clean ? '25/25 signed' : `${d.passed}/25 signed (${d.missed} missed)`}`}
              style={{
                aspectRatio: '1 / 1',
                background: clean ? T.allow : T.caution,
                opacity: clean ? 0.85 : 0.95,
                cursor: 'help', position: 'relative',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                overflow: 'hidden',
              }}>
              {/* Hover-revealed micro-seal in the cell */}
              {typeof SeedSigil === 'function' && (
                <div style={{
                  position: 'absolute', inset: 0,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  opacity: 0.32, mixBlendMode: 'screen', pointerEvents: 'none',
                }}>
                  <SeedSigil seed={seed} size={28} variant="mark"
                    colors={{ ring: clean ? '#0a0a0a' : '#0a0a0a',
                              field: clean ? '#0a0a0a' : '#0a0a0a',
                              core:  clean ? '#0a0a0a' : '#0a0a0a' }}/>
                </div>
              )}
            </div>
          );
        })}
      </div>
      <div style={{
        marginTop: 10, display: 'flex', justifyContent: 'space-between',
        fontFamily: GMONO, fontSize: 9, color: T.muteInv, letterSpacing: 0.4,
      }}>
        <span>{days[0].dateLabel}</span>
        <span>today</span>
      </div>
      <p style={{
        margin: '12px 0 0', fontSize: 11.5, lineHeight: 1.5,
        color: T.muteInv, letterSpacing: -0.05,
      }}>
        Green = all 25 signed in time. Orange = at least one agent missed the window. Honest reporting beats perfect-looking pretense.
      </p>
    </div>
  );
}

Object.assign(window, { Heartbeat });
