/* global React, OB_DATA */
const { useState: useStateT, useMemo: useMemoT } = React;

/* ====================================================
   SCREEN: Library
   ==================================================== */
function Library({ onNav, focused }) {
  const { LIBRARY } = window.OB_DATA;

  const PIECES = {
    mission: {
      title: 'LEAF\u2019s mission and village approach',
      cat: 'LEAF',
      body: (
        <>
          <p>LEAF stands for <em>Locally Encouraging All to Flourish</em>. We work village by village because the texture of a place &mdash; who lives there, what they need, what they already do for one another &mdash; is the only reliable starting point for help that actually helps.</p>
          <p>In Ledbury, we steward St Katherine&rsquo;s Hall: an eight-hundred-year-old chapel in the centre of the town, kept open as a place of gathering, hospitality, and shelter. The Community Voice is one of the rooms attached to that work.</p>
          <p>Our charity number is 1194047. Our trustees meet quarterly. Major decisions about the hall are theirs to take, advised by the host on this site and by the conversations that happen here.</p>
        </>
      )
    },
    heritage: {
      title: 'Eight hundred years of welcome',
      cat: 'St Katherine\u2019s',
      img: 'assets/highstreet.jpg',
      caption: 'Ledbury High Street in the nineteenth century. The hall stood (and stands) a short walk off the road, behind the row of houses on the right.',
      body: (
        <>
          <p>St Katherine&rsquo;s Hall began life in the early thirteenth century as the chapel of a hospital &mdash; in the medieval sense of the word &mdash; serving travellers, the poor, the sick, and the elderly. Saint Katherine of Alexandria was its patron, and the hall&rsquo;s dedication has remained unchanged through dissolution, reformation, restoration, and revival.</p>
          <p>For most of those eight centuries the building has been doing the same kinds of things: feeding people, sheltering people, hosting prayer and meetings, lending its walls to whichever generation needed them.</p>
          <p>Our work today is not the founding of something new but the careful recovery of something old. The hall has changed; its purpose has not.</p>
        </>
      )
    },
    hospital: {
      title: 'Reimagining the medieval hospital',
      cat: 'Concept',
      img: 'assets/chapel1865.jpg',
      caption: 'The hospital chapel as drawn in 1865, looking much as it does today. Founded by Hugh Foliot in 1232 \u2018to the honour of the Lord and of Katherine the Virgin\u2019.',
      body: (
        <>
          <p>The medieval hospital was not, first of all, a place of medicine. It was a place of hospitality &mdash; from the same Latin root, <em>hospes</em>, meaning both guest and host. A hospital was where the weary, the wayfaring, the workless and the unwell could find a place. Care for the body was woven through with welcome of the person.</p>
          <p>That older meaning &mdash; <em>a place to be when there is nowhere else to be</em> &mdash; is what we are reaching for at St Katherine&rsquo;s. Not a clinic. Not a community centre in the bureaucratic sense. A place of welcome, where things happen because people are welcome.</p>
        </>
      )
    },
    foliot: {
      title: 'Hugh Foliot and the founding gift',
      cat: 'St Katherine\u2019s',
      img: 'assets/foliot.jpg',
      caption: 'Hugh Foliot, Bishop of Hereford, from a late-sixteenth-century painted panel placed over the fireplace of the solar by Edward Cooper, master of the hospital.',
      body: (
        <>
          <p>In 1232, Hugh Foliot, Bishop of Hereford, gave land and endowment for a hospital at Ledbury &mdash; <em>to the honour of the Lord and of Katherine the Virgin</em>. The gift was for the care of travellers, the elderly, and the poor of the town. It was a working institution, not a monument.</p>
          <p>Foliot&rsquo;s instinct &mdash; that a town should hold a place open for those who have no other place &mdash; is the instinct we are trying to recover. Eight centuries on, the question is not whether the gift was wise but whether we are still using it.</p>
          <p>The site, the foundation, the dedication, the purpose: all four still hold. What changes, generation to generation, is what the welcome looks like in practice.</p>
        </>
      )
    },
    plan: {
      title: 'The chapel, the hall, and the mezzanine',
      cat: 'The building',
      img: 'assets/floorplan.jpg',
      caption: 'Plan of St Katherine\u2019s Chapel and Hall \u2014 the long thirteenth-century range, with the chapel at the east end and the open hall to the west.',
      body: (
        <>
          <p>The hall is one long thirteenth-century range. At the east end is the chapel &mdash; small, intact, still consecrated, still used. To the west, separated by a low timber partition, is the open hall: a single high-ceilinged room, stone-walled, generous in scale and acoustically alive.</p>
          <p>It is the open hall that does most of the daily work: classes, lectures, parish suppers, coffee mornings, the occasional concert. The chapel is its quieter sibling &mdash; for prayer, for small gatherings, for the moments when a room of forty is too many.</p>
          <p>Our nearest priority is a <strong>mezzanine</strong> along the western third of the hall, sympathetically built into the stonework, which would give us a quiet upper room for committee meetings, one-to-ones, small study groups, and the working life of the site &mdash; without taking the main hall out of public service. That is what we are currently raising funds toward, and what the Community Voice is, in part, helping to shape.</p>
        </>
      )
    },
    ownership: {
      title: 'Proposing, claiming, owning \u2014 how an idea moves',
      cat: 'How we work',
      body: (
        <>
          <p>An idea can begin in two ways. Some are brought into the room by a member, who posts them directly. Others are imported in bulk by LEAF after a conversation, workshop, or stakeholder discussion. Either way, the idea lands as <em>Proposed</em>, with a clear proposer attached.</p>
          <p>An idea brought by a member is automatically owned by them. An idea imported by LEAF arrives <strong>unclaimed</strong> \u2014 visible to all, open for comment and ranking, but waiting for a member to take it on. We mark these openly: <em>this idea is looking for an owner</em>.</p>
          <p><strong>To claim an idea</strong>, a member tells LEAF they\u2019d like to own it, with a short note about why. LEAF reads each request and approves it (or comes back with a question). It is a small, kind gate \u2014 not because ideas are precious, but because owning one is a real commitment.</p>
          <p><strong>What owners do.</strong> An owner refines the wording over time, responds to questions in the discussion, and helps LEAF move the idea forward when the moment is right. Owners can edit the idea\u2019s text directly; their refinements appear with a quiet timestamp so the history is visible.</p>
          <p><strong>Co-ownership is welcome.</strong> Most good ideas have more than one person behind them. A second or third owner can ask to join, and LEAF approves the same way.</p>
          <p><strong>Stepping back is fine.</strong> If life gets busy, an owner can release the idea. It returns to the room as unclaimed, with all its history intact, and someone else can pick it up.</p>
          <p>Stage changes \u2014 <em>Under Consideration</em>, <em>In Development</em>, <em>Decided</em>, <em>Archived</em> \u2014 remain with LEAF. Ownership is about the idea\u2019s shape; stage is about the hall\u2019s decision.</p>
        </>
      )
    },
    ranking: {
      title: 'On ranking — and what it is not',
      cat: 'How we work',
      body: (
        <>
          <p>You will be asked to rank ideas you&rsquo;ve read on two simple scales: <strong>Need</strong> (does this matter for the community?) and <strong>Practicality</strong> (could we actually do it?). The data is collected, visible, and exportable.</p>
          <p><em>This is not voting.</em> LEAF is funding the work and remains the decision-maker. Rankings inform LEAF&rsquo;s discernment; they do not commit it. We are transparent with members about this from the start, and this clarity is what protects the relationship.</p>
          <p>The rankings are most useful when they are honest. Don&rsquo;t inflate your own ideas. Don&rsquo;t downvote ones you don&rsquo;t personally like. Tell us what you actually think a thoughtful neighbour, weighing the room&rsquo;s many possibilities, would say. That is the ranking we can use.</p>
        </>
      )
    },
    host: {
      title: 'LEAF as host, not chair',
      cat: 'How we work',
      img: 'assets/aerial.jpg',
      caption: 'The St Katherine\u2019s precinct from above \u2014 the hospital site sits within the white line, tucked behind the High Street, with the parish church beyond.',
      body: (
        <>
          <p>LEAF is host, not chair of a committee. The space exists because LEAF is funding and stewarding St Katherine&rsquo;s Hall. Members are guests who have been invited to think with us, and whose ideas LEAF will consider carefully and decide on transparently.</p>
          <p>This is the structural protection against the platform becoming a place where unmet expectations curdle into resentment. The agreement is clear from the moment someone is invited: speak freely, rank honestly, expect to be heard, but not to vote.</p>
          <p>When LEAF responds &mdash; in a comment, a status change, or a published decision &mdash; we will say what we have decided and, as far as we are able, why. Where we cannot, we will say so honestly.</p>
        </>
      )
    }
  };

  if (focused && PIECES[focused]) {
    const p = PIECES[focused];
    return (
      <div className="page narrow">
        <button className="back-link" onClick={() => onNav('library')}>← Back to Library</button>
        <div className="page-head">
          <div className="eyebrow">{p.cat}</div>
          <h1 style={{ fontSize: 44, fontStyle: 'italic', fontWeight: 400 }}>{p.title}</h1>
        </div>
        {p.img && (
          <figure className="heritage-figure">
            <img src={p.img} alt="" />
            {p.caption && <figcaption>{p.caption}</figcaption>}
          </figure>
        )}
        <div className="prose first-letter">{p.body}</div>
      </div>
    );
  }

  return (
    <div className="page">
      <div className="page-head">
        <div className="eyebrow">Library</div>
        <h1 style={{ fontStyle: 'italic', fontWeight: 400 }}>What new members already know after a week</h1>
        <p className="lede">Background reading for the room. The mission, the building, the symbol, and how the work here is done.</p>
      </div>

      <div className="library-grid">
        {LIBRARY.map(item => (
          <div key={item.id} className="library-item" onClick={() => onNav('lib:' + item.id)}>
            <div className="cat">{item.cat}</div>
            <h3>{item.title}</h3>
            <p>{item.teaser}</p>
            <div style={{ marginTop: 14, fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--accent)' }}>
              Read →
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ====================================================
   SCREEN: Members directory
   ==================================================== */
function Members({ onInvite, nameMode, isLeaf }) {
  const { MEMBERS } = window.OB_DATA;
  const [inviteEmail, setInviteEmail] = useStateT('');
  const [inviteFirst, setInviteFirst] = useStateT('');
  const [inviteLast, setInviteLast] = useStateT('');
  const [inviteRole, setInviteRole] = useStateT('');
  const [inviteIsLeaf, setInviteIsLeaf] = useStateT(false);
  const [showInvite, setShowInvite] = useStateT(false);
  const [sent, setSent] = useStateT(false);
  const [sending, setSending] = useStateT(false);
  const [errorMsg, setErrorMsg] = useStateT(null);

  const reset = () => {
    setInviteEmail(''); setInviteFirst(''); setInviteLast('');
    setInviteRole(''); setInviteIsLeaf(false); setSent(false); setErrorMsg(null);
  };

  const send = async () => {
    setSending(true); setErrorMsg(null);
    const res = await onInvite(inviteEmail, '', {
      first: inviteFirst.trim() || inviteEmail.split('@')[0],
      last: inviteLast.trim(),
      role: inviteRole.trim(),
      isLeaf: inviteIsLeaf
    });
    setSending(false);
    if (res && res.ok) { setSent(true); }
    else if (res && res.error) { setErrorMsg(res.error); }
  };

  const visible = MEMBERS.filter(m => m.id !== 'leaf');

  return (
    <div className="page">
      <div className="page-head" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow">Members</div>
          <h1 style={{ fontStyle: 'italic', fontWeight: 400 }}>Who&rsquo;s in the room</h1>
          <p className="lede">A small, invited room. {visible.length} members so far.</p>
        </div>
        {isLeaf && (
          <button className="btn btn-primary" onClick={() => { setShowInvite(true); setSent(false); }}>
            <span style={{ fontSize: 18, lineHeight: 0.5 }}>+</span> Invite a new member
          </button>
        )}
      </div>

      {showInvite && (
        <div className="card" style={{ marginBottom: 24, background: 'var(--olive-tint)', borderColor: 'var(--olive-soft)' }}>
          <h4 style={{ color: 'var(--olive-deep)' }}>Send an invitation</h4>
          {errorMsg && (
            <div className="signin-error" style={{ marginTop: 10 }}>{errorMsg}</div>
          )}
          {!sent ? (
            <>
              <label className="field" style={{ marginTop: 14 }}>
                <span className="lbl" style={{ color: 'var(--olive-deep)' }}>Their email</span>
                <input value={inviteEmail} onChange={e => setInviteEmail(e.target.value)} placeholder="friend@example.org" />
              </label>
              <div style={{ display: 'flex', gap: 12 }}>
                <label className="field" style={{ flex: 1 }}>
                  <span className="lbl" style={{ color: 'var(--olive-deep)' }}>First name</span>
                  <input value={inviteFirst} onChange={e => setInviteFirst(e.target.value)} placeholder="Eleanor" />
                </label>
                <label className="field" style={{ flex: 1 }}>
                  <span className="lbl" style={{ color: 'var(--olive-deep)' }}>Last name <span style={{ opacity: 0.6 }}>(optional)</span></span>
                  <input value={inviteLast} onChange={e => setInviteLast(e.target.value)} />
                </label>
              </div>
              <label className="field">
                <span className="lbl" style={{ color: 'var(--olive-deep)' }}>Role / affiliation <span style={{ opacity: 0.6 }}>(optional)</span></span>
                <input value={inviteRole} onChange={e => setInviteRole(e.target.value)} placeholder="e.g. Ledbury Town Council, Resident" />
              </label>
              <label style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6, marginBottom: 14, fontSize: 13, color: 'var(--olive-deep)' }}>
                <input type="checkbox" checked={inviteIsLeaf} onChange={e => setInviteIsLeaf(e.target.checked)} />
                <span>Invite as a LEAF host (can change stages, approve claims, import in bulk)</span>
              </label>
              <div style={{ display: 'flex', gap: 10 }}>
                <button
                  className="btn btn-primary"
                  disabled={!inviteEmail || sending}
                  onClick={send}
                >
                  {sending ? 'Sending\u2026' : 'Send invitation'}
                </button>
                <button className="btn btn-secondary" onClick={() => { setShowInvite(false); reset(); }}>Cancel</button>
              </div>
            </>
          ) : (
            <div>
              <p style={{ color: 'var(--olive-deep)', fontFamily: 'var(--serif)', fontSize: 18, fontStyle: 'italic', marginTop: 6 }}>
                Invitation sent. They&rsquo;ll see a sign-in link in their inbox.
              </p>
              <div style={{ display: 'flex', gap: 10 }}>
                <button className="btn btn-secondary btn-sm" onClick={reset}>Invite someone else</button>
                <button className="btn btn-ghost btn-sm" onClick={() => { setShowInvite(false); reset(); }}>Done</button>
              </div>
            </div>
          )}
        </div>
      )}

      <div className="member-grid">
        {visible.map(m => (
          <div key={m.id} className="member-card">
            <Avatar member={m} size="lg" />
            <div className="who">
              <h4>{displayName(m, nameMode)}</h4>
              <div className="role">{m.role}</div>
              <div className="bio">{m.bio}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ====================================================
   SCREEN: LEAF admin
   ==================================================== */

/* ---- Insights panel: self-contained, fetches /api/admin/insights ---- */
function InsightsPanel() {
  const [data, setData] = React.useState(null);
  const [state, setState] = React.useState('loading'); // loading | ready | error

  React.useEffect(() => {
    let live = true;
    window.api.insights()
      .then((d) => { if (live) { setData(d); setState('ready'); } })
      .catch(() => { if (live) setState('error'); });
    return () => { live = false; };
  }, []);

  if (state === 'loading') {
    return (
      <div className="admin-section">
        <div className="admin-section-head">
          <h3>Insights</h3>
          <span className="admin-section-sub">Reading the room…</span>
        </div>
        <div className="ins-empty">Gathering the latest numbers…</div>
      </div>
    );
  }
  if (state === 'error' || !data) {
    return (
      <div className="admin-section">
        <div className="admin-section-head">
          <h3>Insights</h3>
          <span className="admin-section-sub">The monthly read on who’s here and what the room values.</span>
        </div>
        <div className="ins-empty">Insights couldn’t load just now. Refresh the page to try again.</div>
      </div>
    );
  }

  const { health, activity, participation, nudge, leaderboard } = data;
  const month = new Date(data.generatedAt).toLocaleString('en-GB', { month: 'long', year: 'numeric' });

  // Bars are scaled against the membership total so they read honestly.
  const denom = Math.max(activity.total, 1);
  const pct = (n) => Math.round((n / denom) * 100);

  // Participation donut.
  const partTotal = Math.max(participation.contributors + participation.signedInOnly, 1);
  const contribPct = Math.round((participation.contributors / partTotal) * 100);

  // Sign-in sparkline, normalised to its own peak.
  const peak = Math.max(1, ...activity.signinsByDay);

  // Score dots: filled = rounded average (1..5).
  const dots = (avg, kind) => {
    const filled = Math.max(0, Math.min(5, Math.round(avg)));
    return Array.from({ length: 5 }, (_, i) => (
      <span key={i} className={'d' + (i < filled ? ' f-' + kind : '')}></span>
    ));
  };

  return (
    <div className="admin-section">
      <div className="admin-section-head">
        <h3>Insights</h3>
        <span className="admin-section-sub">The monthly read on who’s here, who’s taking part, and what the room values.</span>
      </div>

      <div className="insights-intro">
        <p>A quick review in five panels: <strong>is the membership healthy</strong>, <strong>are people showing up</strong>, <strong>are they taking part</strong>, <strong>who needs a nudge</strong>, and <strong>what ideas the community values most</strong>.</p>
        <span className="period">{month}</span>
      </div>

      {/* E · Health snapshot */}
      <div className="ins-band">
        <div className="cell"><div className="n">{health.members}</div><div className="l">Members</div><div className="sub">+{health.newThisMonth} this month</div></div>
        <div className="cell"><div className="n">{health.ideas}</div><div className="l">Ideas</div><div className="sub">{health.unclaimed} unclaimed</div></div>
        <div className="cell"><div className="n">{health.comments}</div><div className="l">Comments</div><div className="sub">{health.commentsThisMonth} this month</div></div>
        <div className="cell"><div className="n">{health.rankings}</div><div className="l">Rankings cast</div><div className="sub">across {health.ideas} ideas</div></div>
        <div className="cell"><div className="n">{health.liveSessions}</div><div className="l">Signed in now</div><div className="sub">live sessions</div></div>
      </div>

      <div className="ins-grid">

        {/* B · Showing up */}
        <div className="ins-card">
          <div className="ins-eyebrow"><span className="ltr">B</span><span className="src">sessions · last seen</span></div>
          <div className="title">Showing up</div>
          <div className="cap">How many members are actually active.</div>
          <div className="bars">
            <div className="bar-row">
              <div className="bar-top"><span>Active this week</span><b>{activity.activeWeek}</b></div>
              <div className="bar-track"><div className="bar-fill now" style={{ width: pct(activity.activeWeek) + '%' }}></div></div>
            </div>
            <div className="bar-row">
              <div className="bar-top"><span>Active this month</span><b>{activity.activeMonth}</b></div>
              <div className="bar-track"><div className="bar-fill month" style={{ width: pct(activity.activeMonth) + '%' }}></div></div>
            </div>
            <div className="bar-row">
              <div className="bar-top"><span>Dormant (30 days+)</span><b>{activity.dormant}</b></div>
              <div className="bar-track"><div className="bar-fill dorm" style={{ width: pct(activity.dormant) + '%' }}></div></div>
            </div>
            <div className="bar-row">
              <div className="bar-top"><span>Never signed in</span><b>{activity.neverIn}</b></div>
              <div className="bar-track"><div className="bar-fill never" style={{ width: pct(activity.neverIn) + '%' }}></div></div>
            </div>
          </div>
          <div className="spark-wrap">
            <div className="spark-lbl">Sign-ins · last 14 days</div>
            <div className="spark">
              {activity.signinsByDay.map((v, i) => (
                <span key={i}
                  className={i === activity.signinsByDay.length - 1 ? 'today' : ''}
                  style={{ height: Math.max(4, Math.round((v / peak) * 100)) + '%' }}></span>
              ))}
            </div>
          </div>
        </div>

        {/* C · Using vs looking */}
        <div className="ins-card">
          <div className="ins-eyebrow"><span className="ltr">C</span><span className="src">rankings + comments + ideas</span></div>
          <div className="title">Using vs. looking</div>
          <div className="cap">Of those who’ve signed in, who actually takes part.</div>
          <div className="donut-wrap">
            <div className="donut" style={{ background: 'conic-gradient(var(--olive) 0 ' + contribPct + '%, var(--accent-soft) ' + contribPct + '% 100%)' }}>
              <div className="mid"><b>{contribPct}%</b><span>take part</span></div>
            </div>
            <div className="donut-legend">
              <div className="leg-row"><span className="swatch sw-contrib"></span>Contributors<b>{participation.contributors} members</b></div>
              <div className="leg-row"><span className="swatch sw-quiet"></span>Signed in only<b>{participation.signedInOnly} members</b></div>
            </div>
          </div>
          <div className="ins-foot">Reading isn’t tracked yet — “taking part” means a recorded ranking, comment, or idea.</div>
        </div>

        {/* A · Worth a nudge */}
        <div className="ins-card">
          <div className="ins-eyebrow"><span className="ltr">A</span><span className="src">members × first sign-in</span></div>
          <div className="title">Worth a nudge</div>
          <div className="cap">Invited but never arrived, or gone quiet.</div>
          {nudge.length === 0 ? (
            <div className="ins-empty small">Everyone’s been in recently. Nothing to chase.</div>
          ) : (
            <div className="nudge">
              {nudge.map((m) => (
                <div className="nudge-row" key={m.id}>
                  <span className="avatar sm">{m.initials}</span>
                  <div className="who">
                    <div className="nm">{m.name}</div>
                    <div className="rl">{m.tag === 'quiet' && m.weeksAgo != null
                      ? (m.role ? m.role + ' · ' : '') + 'last in ' + m.weeksAgo + ' wk' + (m.weeksAgo === 1 ? '' : 's') + ' ago'
                      : (m.role || 'Invited member')}</div>
                  </div>
                  <span className={'nudge-tag ' + m.tag}>{m.tag === 'never' ? 'Never in' : 'Quiet'}</span>
                </div>
              ))}
            </div>
          )}
        </div>

      </div>

      {/* D · Idea leaderboard */}
      <div className="ins-card">
        <div className="ins-eyebrow"><span className="ltr">D</span><span className="src">need × practicality</span></div>
        <div className="title">What the room values most</div>
        <div className="cap">Top ideas by average Need and Practicality, with how many members have ranked each.</div>
        {leaderboard.length === 0 ? (
          <div className="ins-empty small">No rankings cast yet.</div>
        ) : (
          <div className="board">
            {leaderboard.map((i, idx) => (
              <div className="board-row" key={i.id}>
                <div className="board-rank">{idx + 1}</div>
                <div className="board-main">
                  <div className="ti">{i.title}</div>
                  <div className="th">{i.theme}</div>
                </div>
                <div className="board-scores">
                  <div className="score">
                    <div className="sl">Need</div>
                    <span className="score-dots">{dots(i.needAvg, 'need')}</span>
                    <span className="val">{i.needAvg.toFixed(1)}</span>
                  </div>
                  <div className="score">
                    <div className="sl">Practical</div>
                    <span className="score-dots">{dots(i.practicalityAvg, 'prac')}</span>
                    <span className="val">{i.practicalityAvg.toFixed(1)}</span>
                  </div>
                </div>
                <div className="board-votes"><b>{i.votes}</b><span>ranked</span></div>
              </div>
            ))}
          </div>
        )}
      </div>

    </div>
  );
}

function Admin({ ideas, pendingClaims, onChangeStage, onSetResponse, onNav, onApproveClaim, onDeclineClaim, onImportIdeas, showToast }) {
  const { MEMBER_BY_ID, STAGES, THEMES } = window.OB_DATA;
  const [editing, setEditing] = useStateT(null); // idea id
  const [draft, setDraft] = useStateT('');
  const [importOpen, setImportOpen] = useStateT(false);
  const [importText, setImportText] = useStateT('');
  const [importPreview, setImportPreview] = useStateT(null);

  // Pending claims come from the dedicated LEAF endpoint (separate
  // from the ideas list, so the queue can populate even before the
  // user opens any specific idea). Each entry has {ideaId,
  // ideaTitle, memberId, message, when, createdAt}.
  const allClaims = (pendingClaims || []).map(c => ({
    idea: ideas.find(i => i.id === c.ideaId) || { id: c.ideaId, title: c.ideaTitle },
    request: c
  }));

  /* ===== Bulk-import parser =====
     Format: ideas separated by blank lines. Each idea is a block of
     `Field: value` lines. Recognised fields: Title, Summary, Theme,
     What, Who, Needs, Proposer (free text).
     Long fields may continue on indented lines or until the next Field:.
  */
  const parseImport = (text) => {
    const blocks = text.split(/\n\s*\n+/).map(b => b.trim()).filter(Boolean);
    const out = [];
    const FIELDS = ['title', 'summary', 'theme', 'what', 'who', 'needs', 'proposer'];
    blocks.forEach(block => {
      const lines = block.split('\n');
      const fields = {};
      let current = null;
      lines.forEach(line => {
        const m = line.match(/^\s*([A-Za-z]+)\s*:\s*(.*)$/);
        if (m && FIELDS.includes(m[1].toLowerCase())) {
          current = m[1].toLowerCase();
          fields[current] = m[2];
        } else if (current) {
          fields[current] = (fields[current] + ' ' + line.trim()).trim();
        }
      });
      if (fields.title) {
        out.push({
          title: fields.title || '',
          summary: fields.summary || '',
          theme: THEMES.includes(fields.theme) ? fields.theme : THEMES[0],
          what: fields.what || '',
          who: fields.who || '',
          needs: fields.needs || '',
          proposer: fields.proposer || 'From the stakeholder discussion \u00b7 1 May 2026'
        });
      }
    });
    return out;
  };

  const handlePreview = () => {
    const parsed = parseImport(importText);
    setImportPreview(parsed);
  };

  const handleConfirmImport = () => {
    if (importPreview && importPreview.length) {
      onImportIdeas(importPreview);
      setImportText('');
      setImportPreview(null);
      setImportOpen(false);
    }
  };

  return (
    <div className="page wide">
      <div className="page-head">
        <div className="eyebrow">LEAF host · behind the scenes</div>
        <h1 style={{ fontStyle: 'italic', fontWeight: 400 }}>Tending the room</h1>
        <p className="lede">Move ideas between stages, post LEAF responses, moderate claims, and import in bulk.</p>
      </div>

      {/* ===== Insights ===== */}
      <InsightsPanel />

      {/* ===== Pending claims queue ===== */}
      {allClaims.length > 0 && (
        <div className="admin-section">
          <div className="admin-section-head">
            <h3>Pending claims <span className="badge-count">{allClaims.length}</span></h3>
            <span className="admin-section-sub">Members asking to own an idea. Approve to give them edit rights.</span>
          </div>
          <div className="claim-queue-list">
            {allClaims.map(({ idea, request }) => {
              const m = MEMBER_BY_ID[request.memberId];
              return (
                <div key={idea.id + ':' + request.memberId} className="claim-queue-card">
                  <Avatar member={m} size="lg" />
                  <div className="claim-queue-body">
                    <div className="claim-queue-top">
                      <strong>{m?.name}</strong>
                      <span className="queue-when">{request.when}</span>
                    </div>
                    <div className="claim-queue-idea" onClick={() => onNav('idea:' + idea.id)}>
                      wants to own <em>“{idea.title}”</em>
                    </div>
                    {request.message && (
                      <div className="claim-queue-message">“{request.message}”</div>
                    )}
                  </div>
                  <div className="claim-queue-actions">
                    <button className="btn btn-primary btn-sm" onClick={() => onApproveClaim(idea.id, request.memberId)}>Approve</button>
                    <button className="btn btn-secondary btn-sm" onClick={() => onDeclineClaim(idea.id, request.memberId)}>Decline</button>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* ===== Import ideas ===== */}
      <div className="admin-section">
        <div className="admin-section-head">
          <h3>Import ideas in bulk</h3>
          <span className="admin-section-sub">Paste a structured list of ideas from a stakeholder discussion. Each becomes unclaimed, awaiting an owner.</span>
        </div>
        {!importOpen ? (
          <button className="btn btn-primary" onClick={() => setImportOpen(true)}>+ Open import panel</button>
        ) : (
          <div className="import-panel">
            <div className="import-format">
              <div className="import-format-head">Format (separate ideas with a blank line)</div>
              <pre>{`Title: A weekly chess club for older neighbours
Summary: One-line description that sells it over coffee.
Theme: Community & Social
What: A paragraph in your own words about what it would do.
Who: Be specific. Names of groups, ages, situations.
Needs: People, time, equipment, space, money.
Proposer: Suggested by Eleanor (stakeholder discussion, 1 May 2026)`}</pre>
              <div className="import-format-hint">
                Themes: {THEMES.join(' · ')}. Omit Proposer to default to “From the stakeholder discussion · 1 May 2026”.
              </div>
            </div>
            <textarea
              className="import-text"
              rows={14}
              value={importText}
              onChange={e => { setImportText(e.target.value); setImportPreview(null); }}
              placeholder="Paste your ideas here…"
            />
            <div style={{ display: 'flex', gap: 10, marginTop: 12 }}>
              <button className="btn btn-secondary" onClick={handlePreview} disabled={!importText.trim()}>Preview</button>
              {importPreview && importPreview.length > 0 && (
                <button className="btn btn-primary" onClick={handleConfirmImport}>
                  Import {importPreview.length} {importPreview.length === 1 ? 'idea' : 'ideas'}
                </button>
              )}
              <button className="btn btn-ghost" onClick={() => { setImportOpen(false); setImportText(''); setImportPreview(null); }}>Close</button>
            </div>
            {importPreview && importPreview.length === 0 && importText.trim() && (
              <div className="import-warning">No ideas parsed. Check that each block starts with <code>Title:</code> and that blocks are separated by a blank line.</div>
            )}
            {importPreview && importPreview.length > 0 && (
              <div className="import-preview">
                <div className="import-preview-head">Preview — {importPreview.length} {importPreview.length === 1 ? 'idea' : 'ideas'} will be imported as <em>Proposed, unclaimed</em>:</div>
                <ol>
                  {importPreview.map((p, i) => (
                    <li key={i}>
                      <strong>{p.title}</strong> <span className="tag">{p.theme}</span>
                      <div style={{ fontSize: 13, color: 'var(--ink-soft)', marginTop: 2 }}>{p.summary}</div>
                    </li>
                  ))}
                </ol>
              </div>
            )}
          </div>
        )}
      </div>

      {/* ===== All ideas table ===== */}
      <div className="admin-section">
        <div className="admin-section-head">
          <h3>All ideas</h3>
          <span className="admin-section-sub">Move between stages, post LEAF’s response.</span>
        </div>
        <div className="admin-exports">
          <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-faint)' }}>Quick exports</span>
          <a className="btn btn-secondary btn-sm" href="/api/exports/trustee.html" target="_blank" rel="noopener noreferrer">Trustee summary (print)</a>
          <a className="btn btn-secondary btn-sm" href="/api/exports/ideas.csv">All ideas (CSV)</a>
        </div>

        <table className="admin-table">
          <thead>
            <tr>
              <th style={{ width: '30%' }}>Idea</th>
              <th>Owners</th>
              <th>Theme</th>
              <th>Stage</th>
              <th>Need</th>
              <th>Practical</th>
              <th>LEAF response</th>
            </tr>
          </thead>
          <tbody>
            {ideas.map(idea => {
              const owners = (idea.owners || []).map(id => MEMBER_BY_ID[id]).filter(Boolean);
              return (
                <tr key={idea.id} className="row">
                  <td onClick={() => onNav('idea:' + idea.id)} style={{ cursor: 'pointer' }}>
                    <div style={{ fontWeight: 600 }}>{idea.title}</div>
                    <div style={{ fontSize: 11, color: 'var(--ink-faint)', marginTop: 2, fontStyle: 'italic' }}>{idea.proposer}</div>
                  </td>
                  <td style={{ fontSize: 13 }}>
                    {owners.length === 0 ? (
                      <span style={{ color: 'var(--accent)', fontStyle: 'italic' }}>— unclaimed</span>
                    ) : (
                      owners.map(o => o.first).join(', ')
                    )}
                  </td>
                  <td><span className="tag">{idea.theme}</span></td>
                  <td>
                    <select value={idea.stage} onChange={(e) => onChangeStage(idea.id, e.target.value)}>
                      {STAGES.map(s => <option key={s} value={s}>{s}</option>)}
                    </select>
                  </td>
                  <td><RankDots value={idea.rankings.need} /></td>
                  <td><RankDots value={idea.rankings.practicality} terra /></td>
                  <td style={{ maxWidth: 240 }}>
                    {editing === idea.id ? (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                        <textarea value={draft} onChange={e => setDraft(e.target.value)} rows={3} style={{ fontSize: 13, minHeight: 60 }} />
                        <div style={{ display: 'flex', gap: 6 }}>
                          <button className="btn btn-primary btn-sm" onClick={() => { onSetResponse(idea.id, draft); setEditing(null); }}>Save</button>
                          <button className="btn btn-secondary btn-sm" onClick={() => setEditing(null)}>Cancel</button>
                        </div>
                      </div>
                    ) : idea.leafResponse ? (
                      <div style={{ display: 'flex', gap: 6, alignItems: 'flex-start' }}>
                        <div style={{ fontSize: 12, fontStyle: 'italic', color: 'var(--olive-deep)', flex: 1 }}>{idea.leafResponse}</div>
                        <button className="btn btn-ghost btn-sm" style={{ padding: '2px 6px' }} onClick={() => { setEditing(idea.id); setDraft(idea.leafResponse); }}>Edit</button>
                      </div>
                    ) : (
                      <button className="btn btn-secondary btn-sm" onClick={() => { setEditing(idea.id); setDraft(''); }}>Post response</button>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Object.assign(window, { Library, Members, Admin, InsightsPanel });
