// Researcher analytics workspace — TAMU research team only.
// Two sub-views: (1) Team Health analysis  (2) Meeting Effectiveness analysis
// Both support drill-in on a single project from the matrix.
const { useState: useStateRC, useMemo: useMemoRC } = React;

// ----- Open-ended response pool (demo data) -----
const OPEN_RESPONSES_HEALTH = [
  { theme: "Trust & Belonging", sentiment: 1, q: "What's going well on this team?",
    text: "People speak up early when something feels off. Last week a young engineer flagged a sequencing issue and it was treated like a gift, not a complaint." },
  { theme: "Adaptive Execution", sentiment: -1, q: "What's getting in the way?",
    text: "RFI turnaround. We are blocked on three submittals and the design team has not closed the loop in over two weeks." },
  { theme: "Energy & Well-Being", sentiment: 1, q: "What's energizing the team?",
    text: "Pre-task planning is sharp on this job. Crews show up knowing what's expected and we end the day on time." },
  { theme: "Alignment & Ownership", sentiment: -1, q: "What's getting in the way?",
    text: "We have two PMs giving slightly different answers on schedule. It's small but it costs us trust with the subs." },
  { theme: "Capability & Growth", sentiment: 1, q: "What's working?",
    text: "I feel like I'm being stretched in the right way. The senior super lets me try things and walks me through what I missed afterward." },
  { theme: "Trust & Belonging", sentiment: -1, q: "What's getting in the way?",
    text: "Late changes from the owner are bleeding the energy out of the foreman. Morale is flat in the Monday huddle." },
  { theme: "Adaptive Execution", sentiment: 1, q: "What's working?",
    text: "Decision logs are paying off. New folks can catch up in 10 minutes instead of pulling someone aside for an hour." },
  { theme: "Overall", sentiment: 1, q: "Would you work with this team again?",
    text: "Yes. The standard is high but the support is real. That's the bar I want for every job." },
  { theme: "Energy & Well-Being", sentiment: -1, q: "What's getting in the way?",
    text: "Saturdays are getting normalized again. We talked about it but nothing has changed in three weeks." },
];

const OPEN_RESPONSES_MEETING = [
  { sentiment: 1, q: "What worked in this meeting?",
    text: "We left with a list of three things to do and who owns each. That has not always been the case." },
  { sentiment: -1, q: "What could be better?",
    text: "Half the agenda items did not need everyone in the room. Subs sat through 40 minutes that did not concern them." },
  { sentiment: 1, q: "What worked?",
    text: "The owner heard the schedule risk for the first time and asked good questions instead of pushing back." },
  { sentiment: -1, q: "What could be better?",
    text: "We re-litigated decisions from last week. If we trust the decision log we don't need to keep re-opening them." },
  { sentiment: 0, q: "Anything to flag?",
    text: "Audio in the trailer cut out for the remote attendees. Twice. We need to fix that before the OAC on Thursday." },
  { sentiment: 1, q: "What worked?",
    text: "Short, focused, ended early. People had time to actually go execute on what we decided." },
];

// ============================================================
// MAIN RESEARCHER VIEW — sub-tab switcher + drill-in router
// ============================================================
// Scope a team object to a window of weeks. Returns a derived team
// with weekly[] trimmed and current/trend/responseRate recomputed.
function scopeTeam(team, range, latestWeek) {
  if (range === "all") return team;
  const wks = range === "4w" ? 4 : range === "2w" ? 2 : range === "1w" ? 1 : 99;
  const minWeek = Math.max(1, latestWeek - wks + 1);
  const weekly = team.weekly.filter(w => w.week >= minWeek);
  if (!weekly.length) return team;
  const last = weekly[weekly.length - 1];
  const first = weekly[0];
  const responses = weekly.reduce((s,w)=>s+w.responses, 0);
  // Scope-aware denominator (matches pm.jsx project-card math): each week of the
  // window adds team.size potential responses, e.g. 14-person team over 2 weeks => 28.
  const denom = team.size * weekly.length;
  return {
    ...team,
    weekly,
    current: last.score,
    trend: (last.score != null && first.score != null) ? last.score - first.score : 0,
    responseRate: denom ? responses / denom : team.responseRate,
  };
}

function ResearcherView({ data }) {
  const { teams } = data;
  const [tab, setTab] = useStateRC("health"); // "health" | "meeting"
  const [drillId, setDrillId] = useStateRC(null);
  const [scope, setScope] = useStateRC("all"); // all | 4w | 2w | 1w

  const latestWeek = data.cohortWeekly[data.cohortWeekly.length - 1].week;
  const scopedData = useMemoRC(() => {
    if (scope === "all") return data;
    const wks = scope === "4w" ? 4 : scope === "2w" ? 2 : 1;
    const minWeek = Math.max(1, latestWeek - wks + 1);
    const cohortWeekly = data.cohortWeekly.filter(w => w.week >= minWeek);
    const teamsScoped = data.teams.map(t => scopeTeam(t, scope, latestWeek));
    // Recompute summary.avgHealth from scoped current values
    const summary = {
      ...data.summary,
      avgHealth: r1(avg(teamsScoped.map(t => t.current))),
      weeklyResponsesThisWeek: cohortWeekly.reduce((s,w)=>s+w.responses, 0),
    };
    return { ...data, teams: teamsScoped, cohortWeekly, summary };
  }, [data, scope, latestWeek]);

  if (drillId) {
    // Drill-in always shows full team history regardless of scope
    const team = data.teams.find(t => t.id === drillId);
    return tab === "health"
      ? <HealthProjectDetail team={team} data={data} onBack={() => setDrillId(null)}/>
      : <MeetingProjectDetail team={team} data={data} onBack={() => setDrillId(null)}/>;
  }

  return (
    <div className="space-y-7">
      <div className="flex items-end justify-between flex-wrap gap-4">
        <div>
          <div className="text-[11px] tracking-[0.2em] uppercase" style={{color: "oklch(0.45 0.15 270)"}}>TAMU · Research workspace</div>
          <h1 className="text-[34px] tracking-[-0.02em] font-semibold mt-1.5" style={{color: INK}}>Analysis</h1>
          <p className="text-[15px] mt-1.5" style={{color: SUBINK}}>{teams.length} R-O project teams · Week {data.summary.weeksElapsed} of {data.summary.weeksTotal}</p>
        </div>
        <div className="flex items-center gap-2 flex-wrap">
          <DateRangeScope value={scope} onChange={setScope} latestWeek={latestWeek} studyStart={data.studyStart}/>
          <span className="px-3 py-1.5 border rounded-full text-[12px] tabular-nums" style={{borderColor: RULE, color: SUBINK, background: "white"}}>N = {scopedData.summary.totalResponses}</span>
          <span className="px-3 py-1.5 border rounded-full text-[12px]" style={{borderColor: RULE, color: SUBINK, background: "white"}}>α = 0.88</span>
        </div>
      </div>

      {/* Sub-tab switcher */}
      <div className="inline-flex p-0.5 rounded-full border" style={{background: "white", borderColor: RULE}}>
        <SubTab active={tab === "health"} onClick={() => setTab("health")}
          label="Team Health" sub="Pre / Post"/>
        <SubTab active={tab === "meeting"} onClick={() => setTab("meeting")}
          label="Meeting Effectiveness" sub="Weekly · per-meeting"/>
      </div>

      {tab === "health"
        ? <HealthAnalysis data={scopedData} scope={scope} onSelectTeam={setDrillId}/>
        : <MeetingAnalysis data={scopedData} scope={scope} onSelectTeam={setDrillId}/>}
    </div>
  );
}

// Date range scope picker — 4 presets that scope cohort views
function DateRangeScope({ value, onChange, latestWeek, studyStart }) {
  const opts = [
    { v: "all", label: "All study", sub: `Wk 1–${latestWeek}` },
    { v: "4w", label: "Last 4 weeks", sub: `Wk ${Math.max(1, latestWeek - 3)}–${latestWeek}` },
    { v: "2w", label: "Last 2 weeks", sub: `Wk ${Math.max(1, latestWeek - 1)}–${latestWeek}` },
    { v: "1w", label: "This week", sub: `Wk ${latestWeek}` },
  ];
  const [open, setOpen] = useStateRC(false);
  const cur = opts.find(o => o.v === value);
  return (
    <div className="relative">
      <button onClick={() => setOpen(o => !o)}
        className="inline-flex items-center gap-2 px-3 py-1.5 border rounded-full text-[12px] hover:border-stone-400 transition"
        style={{borderColor: RULE, background: "white", color: INK}}>
        <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke={SUBINK} strokeWidth="1.5"><rect x="2" y="3" width="12" height="11" rx="1.5"/><path d="M2 6h12M5 1.5v3M11 1.5v3"/></svg>
        <span style={{color: SUBINK}}>Range:</span>
        <span className="font-medium">{cur.label}</span>
        <svg width="9" height="9" viewBox="0 0 10 10" fill="none" stroke={SUBINK} strokeWidth="1.5" style={{transform: open ? "rotate(180deg)" : "none", transition: "transform 0.15s"}}><path d="M2 4l3 3 3-3"/></svg>
      </button>
      {open && (
        <>
          <div className="fixed inset-0 z-30" onClick={() => setOpen(false)}/>
          <div className="absolute right-0 mt-2 z-40 rounded-2xl bg-white border shadow-xl overflow-hidden" style={{borderColor: RULE, width: 240}}>
            <div className="px-4 py-2.5 border-b text-[10.5px] tracking-[0.16em] uppercase" style={{borderColor: RULE, color: SUBINK, background: PAPER_2}}>Scope analysis to</div>
            {opts.map(o => (
              <button key={o.v}
                onClick={() => { onChange(o.v); setOpen(false); }}
                className="w-full text-left px-4 py-2.5 flex items-center gap-3 border-b last:border-b-0 hover:bg-stone-50 transition"
                style={{borderColor: RULE, background: o.v === value ? PAPER_2 : "transparent"}}>
                <span className="size-1.5 rounded-full shrink-0" style={{background: o.v === value ? INK : "transparent", border: o.v === value ? "none" : `1px solid ${SUBINK}`}}/>
                <div className="flex-1">
                  <div className="text-[12.5px] font-medium" style={{color: INK}}>{o.label}</div>
                  <div className="text-[11px] font-mono tabular-nums" style={{color: SUBINK}}>{o.sub}</div>
                </div>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

function SubTab({ active, onClick, label, sub }) {
  return (
    <button onClick={onClick}
      className={"px-5 py-2.5 rounded-full transition text-left " + (active ? "bg-stone-900 shadow-sm" : "")}
      style={active ? {background: INK} : {}}>
      <div className="text-[13px] tracking-tight font-medium" style={{color: active ? "white" : INK}}>{label}</div>
      <div className="text-[10.5px] tracking-tight leading-tight mt-0.5" style={{color: active ? "oklch(0.85 0.005 80)" : SUBINK}}>{sub}</div>
    </button>
  );
}

// ============================================================
// TEAM HEALTH ANALYSIS
// ============================================================
function HealthAnalysis({ data, onSelectTeam }) {
  const { teams, dimensions, summary, cohortWeekly, cohortDimensions } = data;
  const [riskFilter, setRiskFilter] = useStateRC("all");
  const [sortBy, setSortBy] = useStateRC("health-asc");

  const filtered = useMemoRC(() => {
    let arr = [...teams];
    if (riskFilter !== "all") arr = arr.filter((t) => t.risk === riskFilter);
    arr.sort((a, b) => {
      if (sortBy === "health-asc") return a.current - b.current;
      if (sortBy === "health-desc") return b.current - a.current;
      if (sortBy === "trend") return a.trend - b.trend;
      if (sortBy === "response") return a.responseRate - b.responseRate;
      return a.name.localeCompare(b.name);
    });
    return arr;
  }, [teams, sortBy, riskFilter]);

  // Pre / Post for cohort
  const preTeams = teams.filter(t => t.preDone);
  const postTeams = teams.filter(t => t.postDone);
  const avgPre = preTeams.length ? avg(preTeams.map(t=>t.preScore)) : null;
  const avgPost = postTeams.length ? avg(postTeams.map(t=>t.postScore)) : null;

  return (
    <div className="space-y-7">
      {/* Top KPI strip — 5 stats: Pre + Post + Reliability + cohort scale */}
      <div className="grid grid-cols-5 gap-3">
        <BigStat label="Project teams" value={summary.teamCount} sub="in study"/>
        <BigStat label="Baseline Health" value={avgPre != null ? avgPre.toFixed(1) : "—"} sub={`${summary.preComplete}/${summary.teamCount} complete`}/>
        <BigStat label="Post Health" value={avgPost != null ? avgPost.toFixed(1) : "—"} sub={`${summary.postComplete}/${summary.teamCount} complete`}/>
        <BigStat label="This week" value={summary.weeklyResponsesThisWeek} sub={`of ${summary.totalResponses} total`}/>
        <BigStat label="Cronbach α" value="0.88" sub="reliability"/>
      </div>

      {/* Trend + Pre/Post comparison + risk distribution */}
      <div className="grid grid-cols-12 gap-5">
        <Card className="col-span-8">
          <CardHeader title="Cohort health trend" subtitle="Response-weighted weekly mean · pre baseline → post baseline"/>
          <CardBody>
            <div className="overflow-x-auto"><PrePostTrendLine cohortWeekly={cohortWeekly} avgPre={avgPre} avgPost={avgPost} width={760} height={240}/></div>
            <div className="mt-4 flex items-center gap-7 text-[13px] flex-wrap">
              <KV label="Baseline" v={avgPre != null ? avgPre.toFixed(1) : "—"}/>
              <KV label="Current" v={cohortWeekly[cohortWeekly.length-1].score != null ? cohortWeekly[cohortWeekly.length-1].score.toFixed(1) : '—'}/>
              <KV label="Post" v={avgPost != null ? avgPost.toFixed(1) : "—"}/>
              <KV label="Δ from baseline" v={avgPre != null ? signedN(cohortWeekly[cohortWeekly.length-1].score - avgPre) : "—"}/>
            </div>
          </CardBody>
        </Card>
        <Card className="col-span-4">
          <CardHeader title="Risk distribution" subtitle="Auto-classified by score, trend, response"/>
          <CardBody><RiskRing summary={summary} teams={teams} onSelectTeam={onSelectTeam}/></CardBody>
        </Card>

        <Card className="col-span-12">
          <CardHeader title="Construct averages" subtitle="6-dimension framework · cohort mean · 1–5 Likert distribution"/>
          <CardBody>
            <div className="grid grid-cols-6 gap-3">
              {cohortDimensions.map((d) => (
                <div key={d.id} className="rounded-2xl p-4 border" style={{borderColor: RULE, background: PAPER}}>
                  <div className="flex items-baseline justify-between gap-2">
                    <span className="text-[10.5px] tracking-[0.12em] uppercase" style={{color: SUBINK}}>{d.short}</span>
                    <span className="text-[26px] tracking-tight font-semibold tabular-nums" style={{color: healthColor(d.avg)}}>{d.avg != null ? d.avg.toFixed(1) : '—'}</span>
                  </div>
                  <div className="text-[12.5px] mt-1.5 leading-snug" style={{color: INK}}>{d.label}</div>
                  <div className="text-[10.5px] mt-0.5" style={{color: SUBINK}}>{d.questionRange} · {d.items} items</div>
                  <div className="mt-3"><DistBar buckets={d.distribution}/></div>
                  <div className="text-[10.5px] flex justify-between mt-1.5 tabular-nums" style={{color: SUBINK}}>
                    <span>min {d.min != null ? d.min.toFixed(1) : '—'}</span>
                    <span>max {d.max != null ? d.max.toFixed(1) : '—'}</span>
                    <span>σ {(() => { const vals = teams.map(t => t.dimensions.find(dd=>dd.id===d.id)?.current).filter(v => v != null); return vals.length ? stddev(vals).toFixed(2) : '—'; })()}</span>
                  </div>
                </div>
              ))}
            </div>
          </CardBody>
        </Card>
      </div>

      {/* Team × Construct matrix */}
      <div>
        <div className="flex items-end justify-between gap-4 flex-wrap mb-4">
          <div>
            <h2 className="text-[20px] tracking-[-0.01em] font-semibold" style={{color: INK}}>Team × Construct matrix</h2>
            <p className="text-[13px] mt-0.5" style={{color: SUBINK}}>{filtered.length} of {teams.length} projects · click any project to drill into its breakdown</p>
          </div>
          <div className="flex items-center gap-2 flex-wrap">
            <Segmented value={riskFilter} onChange={setRiskFilter}
              options={[
                { v: "all", label: `All ${teams.length}` },
                { v: "critical", label: `Crit ${summary.riskCounts.critical}` },
                { v: "watch", label: `Watch ${summary.riskCounts.watch}` },
                { v: "healthy", label: `Healthy ${summary.riskCounts.healthy}` },
              ]}/>
            <Select value={sortBy} onChange={setSortBy}
              options={[
                { v: "health-asc", label: "Sort: Health ↑" },
                { v: "health-desc", label: "Sort: Health ↓" },
                { v: "trend", label: "Sort: Trend" },
                { v: "response", label: "Sort: Response rate" },
                { v: "name", label: "Sort: Name" },
              ]}/>
          </div>
        </div>
        <Card><CardBody className="overflow-x-auto"><HealthMatrix teams={filtered} dimensions={dimensions} onTeamClick={onSelectTeam}/></CardBody></Card>
      </div>

      {/* Open-ended responses panel */}
      <Card>
        <CardHeader title="Open-ended responses" subtitle="Qualitative meeting feedback · anonymized" eyebrow="Live data"/>
        <OpenEndedPanel responses={teams.flatMap(t => (t.themes||[]).map(text => ({text, q: "What would you keep or change?", sentiment: 0, theme: t.short})))} byTheme/>
      </Card>

      {/* Activity log */}
      <Card>
        <CardHeader title="Response activity log" subtitle="Latest submissions across all teams · anonymized"/>
        <ResponseLog teams={teams} kind="health"/>
      </Card>
    </div>
  );
}

// Health-specific matrix — adds Pre + Post columns
function HealthMatrix({ teams, dimensions, onTeamClick }) {
  const cellH = 32, headerH = 38, labelW = 220, cellW = 60, ppW = 50, gap = 14;
  const totalDimsW = dimensions.length * cellW;
  const width = labelW + ppW + gap + totalDimsW + gap + ppW + gap + 50;
  const height = headerH + teams.length * cellH;

  return (
    <svg width={width} height={height} className="block">
      {/* Headers */}
      <text x={labelW + ppW/2} y={headerH-12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">PRE</text>
      {dimensions.map((d, di) => (
        <text key={d.id} x={labelW + ppW + gap + di*cellW + cellW/2} y={headerH-12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">{d.short}</text>
      ))}
      <text x={labelW + ppW + gap + totalDimsW + gap + ppW/2} y={headerH-12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">POST</text>
      <text x={labelW + ppW + gap + totalDimsW + gap + ppW + gap + 25} y={headerH-12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">AVG</text>

      {/* Divider lines under headers */}
      <line x1={labelW + ppW + gap/2} x2={labelW + ppW + gap/2} y1={headerH-26} y2={headerH + teams.length*cellH} stroke={RULE} strokeDasharray="2 3"/>
      <line x1={labelW + ppW + gap + totalDimsW + gap/2} x2={labelW + ppW + gap + totalDimsW + gap/2} y1={headerH-26} y2={headerH + teams.length*cellH} stroke={RULE} strokeDasharray="2 3"/>

      {teams.map((t, ti) => {
        const y = headerH + ti*cellH;
        const tAvg = avg(t.dimensions.map(d=>d.current));
        return (
          <g key={t.id} className="cursor-pointer group" onClick={()=>onTeamClick && onTeamClick(t.id)}>
            <rect x="0" y={y} width={width} height={cellH} fill="transparent" className="group-hover:fill-black/3"/>
            <text x="12" y={y+cellH/2+4} fontSize="12" fill={INK}>{t.short.length > 26 ? t.short.slice(0,26)+"…" : t.short}</text>

            {/* PRE */}
            {t.preDone ? (
              <g>
                <rect x={labelW + 4} y={y+5} width={ppW-8} height={cellH-10} rx="3" fill={healthColor(t.preScore, 0.32)} stroke={healthColor(t.preScore, 0.55)} strokeWidth="1"/>
                <text x={labelW + ppW/2} y={y+cellH/2+4} fontSize="11" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace">{t.preScore != null ? t.preScore.toFixed(1) : '—'}</text>
              </g>
            ) : (
              <g>
                <rect x={labelW + 4} y={y+5} width={ppW-8} height={cellH-10} rx="3" fill="oklch(0.95 0.005 80)" stroke={RULE} strokeWidth="1" strokeDasharray="2 2"/>
                <text x={labelW + ppW/2} y={y+cellH/2+4} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">—</text>
              </g>
            )}

            {/* Dimensions */}
            {dimensions.map((d, di) => {
              const v = t.dimensions[di].current;
              const cx = labelW + ppW + gap + di*cellW;
              return (
                <g key={d.id}>
                  <rect x={cx + 4} y={y+5} width={cellW-8} height={cellH-10} rx="3" fill={healthColor(v, 0.32)} stroke={healthColor(v, 0.55)} strokeWidth="1"/>
                  <text x={cx + cellW/2} y={y+cellH/2+4} fontSize="11" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace">{v != null ? v.toFixed(1) : '—'}</text>
                </g>
              );
            })}

            {/* POST */}
            {t.postDone ? (
              <g>
                <rect x={labelW + ppW + gap + totalDimsW + gap + 4} y={y+5} width={ppW-8} height={cellH-10} rx="3" fill={healthColor(t.postScore, 0.32)} stroke={healthColor(t.postScore, 0.55)} strokeWidth="1"/>
                <text x={labelW + ppW + gap + totalDimsW + gap + ppW/2} y={y+cellH/2+4} fontSize="11" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace">{t.postScore != null ? t.postScore.toFixed(1) : '—'}</text>
              </g>
            ) : (
              <g>
                <rect x={labelW + ppW + gap + totalDimsW + gap + 4} y={y+5} width={ppW-8} height={cellH-10} rx="3" fill="oklch(0.95 0.005 80)" stroke={RULE} strokeWidth="1" strokeDasharray="2 2"/>
                <text x={labelW + ppW + gap + totalDimsW + gap + ppW/2} y={y+cellH/2+4} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">—</text>
              </g>
            )}

            {/* Row average */}
            <text x={labelW + ppW + gap + totalDimsW + gap + ppW + gap + 25} y={y+cellH/2+4} fontSize="11" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace" fontWeight="600">{tAvg != null ? tAvg.toFixed(1) : '—'}</text>
          </g>
        );
      })}
    </svg>
  );
}

// Pre/Post + weekly trend overlay
function PrePostTrendLine({ cohortWeekly, avgPre, avgPost, width = 760, height = 240 }) {
  const padL = 60, padR = 60, padT = 30, padB = 36;
  const w = width - padL - padR, h = height - padT - padB;
  const min = 1, max = 5;
  const ys = v => padT + h - ((v - min) / (max - min)) * h;
  const cols = cohortWeekly.length + 2; // pre + N weeks + post
  const xs = i => padL + (i / (cols - 1)) * w;

  const path = cohortWeekly.map((d, i) => `${i ? "L" : "M"}${xs(i+1)},${ys(d.score)}`).join(" ");
  const last = cohortWeekly[cohortWeekly.length - 1];

  return (
    <svg width={width} height={height} className="block">
      {[1,2,3,4,5].map(v => (
        <g key={v}>
          <line x1={padL} x2={width - padR} y1={ys(v)} y2={ys(v)} stroke={RULE} strokeDasharray={v === 3 ? "" : "2 4"}/>
          <text x={padL - 10} y={ys(v) + 4} fontSize="10" textAnchor="end" fill={SUBINK} fontFamily="JetBrains Mono, monospace">{v}</text>
        </g>
      ))}

      {/* Pre baseline marker */}
      {avgPre != null && (
        <g>
          <circle cx={xs(0)} cy={ys(avgPre)} r="6" fill="white" stroke={SUBINK} strokeWidth="1.6"/>
          <circle cx={xs(0)} cy={ys(avgPre)} r="3" fill={SUBINK}/>
          <text x={xs(0)} y={padT - 14} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">BASELINE</text>
          <text x={xs(0)} y={ys(avgPre) - 10} fontSize="10.5" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace">{avgPre != null ? avgPre.toFixed(1) : '—'}</text>
        </g>
      )}

      {/* Weekly area + line */}
      <path d={path} fill="none" stroke={healthColor(last.score)} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
      {cohortWeekly.map((d, i) => (
        <g key={i}>
          <circle cx={xs(i+1)} cy={ys(d.score)} r="3.5" fill={healthColor(d.score)} stroke="white" strokeWidth="1.5"/>
          <text x={xs(i+1)} y={height - 12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">W{d.week}</text>
        </g>
      ))}

      {/* Post marker */}
      {avgPost != null ? (
        <g>
          <circle cx={xs(cols-1)} cy={ys(avgPost)} r="6" fill="white" stroke={healthColor(avgPost)} strokeWidth="1.6"/>
          <circle cx={xs(cols-1)} cy={ys(avgPost)} r="3" fill={healthColor(avgPost)}/>
          <text x={xs(cols-1)} y={padT - 14} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">POST</text>
          <text x={xs(cols-1)} y={ys(avgPost) - 10} fontSize="10.5" textAnchor="middle" fill={INK} fontFamily="JetBrains Mono, monospace">{avgPost != null ? avgPost.toFixed(1) : '—'}</text>
        </g>
      ) : (
        <g>
          <circle cx={xs(cols-1)} cy={ys(3)} r="6" fill="white" stroke={RULE} strokeWidth="1.4" strokeDasharray="2 2"/>
          <text x={xs(cols-1)} y={padT - 14} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.1em">POST</text>
          <text x={xs(cols-1)} y={ys(3) + 4} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">pending</text>
        </g>
      )}

      {/* Pre / Post column labels at base */}
      <text x={xs(0)} y={height - 12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">Pre</text>
      <text x={xs(cols-1)} y={height - 12} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">Post</text>
    </svg>
  );
}

// ============================================================
// MEETING EFFECTIVENESS ANALYSIS
// ============================================================
function MeetingAnalysis({ data, onSelectTeam }) {
  const { teams, meetingQuestions } = data;
  const [sortBy, setSortBy] = useStateRC("meet-asc");

  const cohortMeetingAvg = avg(teams.map(t => t.meetingAvg));
  const totalMeetings = teams.reduce((s, t) => s + t.meetings.length, 0);
  const totalMeetingResponses = teams.reduce((s, t) => s + t.meetings.reduce((ss, m) => ss + m.responses, 0), 0);
  const totalMeetingAttendees = teams.reduce((s, t) => s + t.meetings.reduce((ss, m) => ss + m.attendees, 0), 0);
  const meetingResponseRate = totalMeetingAttendees ? totalMeetingResponses / totalMeetingAttendees : 0;

  // Item-level cohort means (item index aligned with MEETING_QUESTIONS)
  const itemMeans = meetingQuestions.map((q, qi) => {
    const vals = teams.map(t => t.meetingItems[qi]?.score).filter(v => v != null);
    return { q, score: avg(vals), min: vals.length ? Math.min(...vals) : null, max: vals.length ? Math.max(...vals) : null };
  });

  // Cohort meeting trend — average across teams per meeting index 0..7
  const meetingTrend = Array.from({length: 8}, (_, mi) => {
    const vals = teams.map(t => t.meetings[mi]?.score).filter(v => v != null);
    const s = avg(vals);
    return { i: mi, score: s != null ? r1(s) : null, label: `M${mi+1}` };
  });

  const sorted = useMemoRC(() => {
    const arr = [...teams];
    if (sortBy === "meet-asc") arr.sort((a, b) => a.meetingAvg - b.meetingAvg);
    if (sortBy === "meet-desc") arr.sort((a, b) => b.meetingAvg - a.meetingAvg);
    if (sortBy === "name") arr.sort((a, b) => a.name.localeCompare(b.name));
    return arr;
  }, [teams, sortBy]);

  return (
    <div className="space-y-7">
      {/* KPI strip */}
      <div className="grid grid-cols-6 gap-3">
        <BigStat label="Meetings logged" value={totalMeetings} sub="across cohort"/>
        <BigStat label="Cohort score" value={cohortMeetingAvg != null ? cohortMeetingAvg.toFixed(1) : '—'} sub="weighted mean" accent={healthColor(cohortMeetingAvg)}/>
        <BigStat label="Best item" value={itemMeans.length ? itemMeans.reduce((a,b)=>b.score>a.score?b:a).score.toFixed(1) : "—"} sub={itemMeans.length ? itemMeans.reduce((a,b)=>b.score>a.score?b:a).q.text.slice(0,28)+"…" : ""}/>
        <BigStat label="Weakest item" value={itemMeans.length ? itemMeans.reduce((a,b)=>b.score<a.score?b:a).score.toFixed(1) : "—"} sub={itemMeans.length ? itemMeans.reduce((a,b)=>b.score<a.score?b:a).q.text.slice(0,28)+"…" : ""}/>
        <BigStat label="Meeting responses" value={totalMeetingResponses} sub={`${Math.round(meetingResponseRate*100)}% of attendees`}/>
        <BigStat label="Cronbach α" value="0.91" sub="reliability"/>
      </div>

      {/* Cohort trend + per-project averages list */}
      <div className="grid grid-cols-12 gap-5">
        <Card className="col-span-7">
          <CardHeader title="Cohort meeting trend" subtitle="Average score across all projects · last 8 meetings"/>
          <CardBody>
            <div className="overflow-x-auto"><MeetingTrendChart data={meetingTrend} width={580} height={210}/></div>
          </CardBody>
        </Card>
        <Card className="col-span-5">
          <CardHeader title="Item-level cohort means" subtitle="12 questions · 1–5 Likert"/>
          <CardBody className="!py-3">
            <div className="space-y-2">
              {itemMeans.map((it, i) => (
                <div key={i} className="flex items-center gap-3">
                  <div className="text-[12.5px] flex-1 leading-snug" style={{color: INK}}>{it.q.text || it.q}</div>
                  <Bar value={it.score} width={90} height={6}/>
                  <div className="text-[12px] font-mono tabular-nums w-9 text-right" style={{color: healthColor(it.score)}}>{it.score != null ? it.score.toFixed(1) : '—'}</div>
                </div>
              ))}
            </div>
          </CardBody>
        </Card>
      </div>

      {/* Per-project averages — similar to operations cards */}
      <div>
        <div className="flex items-end justify-between gap-4 flex-wrap mb-4">
          <div>
            <h2 className="text-[20px] tracking-[-0.01em] font-semibold" style={{color: INK}}>Project averages</h2>
            <p className="text-[13px] mt-0.5" style={{color: SUBINK}}>Click any project to see its meeting-level breakdown</p>
          </div>
          <Select value={sortBy} onChange={setSortBy}
            options={[
              { v: "meet-asc", label: "Sort: Score ↑" },
              { v: "meet-desc", label: "Sort: Score ↓" },
              { v: "name", label: "Sort: Name" },
            ]}/>
        </div>
        <div className="grid grid-cols-3 gap-4">
          {sorted.map(t => <MeetingProjectCard key={t.id} team={t} onClick={()=>onSelectTeam(t.id)}/>)}
        </div>
      </div>

      {/* Open-ended responses */}
      <Card>
        <CardHeader title="Open-ended responses" subtitle="Qualitative meeting feedback · anonymized" eyebrow="Live data"/>
        <OpenEndedPanel responses={teams.flatMap(t => (t.themes||[]).map(text => ({text, q: "What would you keep or change?", sentiment: 0, theme: t.short})))}/>
      </Card>

      {/* Activity log */}
      <Card>
        <CardHeader title="Meeting submissions log" subtitle="Latest meeting effectiveness submissions · anonymized"/>
        <ResponseLog teams={teams} kind="meeting"/>
      </Card>
    </div>
  );
}

function MeetingProjectCard({ team, onClick }) {
  const meetings = team.meetings || [];
  if (!meetings.length) return null;
  const last = meetings[meetings.length - 1];
  const first = meetings[0];
  const trend = (last?.score != null && first?.score != null) ? last.score - first.score : 0;
  const totalResp = meetings.reduce((s, m) => s + (m.responses || 0), 0);
  const totalAtt = meetings.reduce((s, m) => s + (m.attendees || 0), 0);
  const respRate = totalAtt ? totalResp / totalAtt : 0;
  const c = healthColor(team.meetingAvg);

  return (
    <button onClick={onClick}
      className="text-left rounded-[20px] bg-white border p-5 transition-all duration-200 group hover:shadow-[0_8px_24px_rgba(0,0,0,0.06)] hover:-translate-y-0.5 hover:border-[oklch(0.78_0.005_250)]"
      style={{borderColor: RULE}}>
      <div className="flex items-start justify-between gap-3 mb-3">
        <div className="min-w-0 flex-1">
          <div className="text-[10px] font-mono tracking-[0.16em] uppercase" style={{color: SUBINK}}>{team.code} · {team.region}</div>
          <div className="text-[15px] tracking-tight font-semibold leading-tight mt-1.5 truncate" style={{color: INK}}>{team.short}</div>
          <div className="text-[12px] mt-1" style={{color: SUBINK}}>{meetings.length} meetings · {totalResp} responses</div>
        </div>
      </div>

      <div className="flex items-baseline gap-2 mt-2">
        <span className="text-[44px] tracking-[-0.03em] font-semibold leading-none tabular-nums" style={{color: c}}>{team.meetingAvg != null ? team.meetingAvg.toFixed(1) : '—'}</span>
        <span className="text-[14px]" style={{color: SUBINK}}>/ 5</span>
        {Math.abs(trend) >= 0.1 && (
          <span className="ml-auto inline-flex items-center gap-0.5 text-[11.5px] font-mono tabular-nums px-1.5 py-0.5 rounded" style={{
            background: trend > 0 ? "oklch(0.96 0.04 145)" : "oklch(0.97 0.03 28)",
            color: trend > 0 ? "oklch(0.42 0.12 145)" : "oklch(0.5 0.14 28)"
          }}>
            <span>{trend > 0 ? "↑" : "↓"}</span>{signedN(trend)}
          </span>
        )}
      </div>
      <div className="text-[12.5px] mt-1.5" style={{color: SUBINK}}>
        Response rate: <span style={{color: INK, fontWeight: 500}}>{Math.round(respRate*100)}%</span>
      </div>

      <div className="mt-4 flex items-end gap-[3px] h-[28px]">
        {meetings.map((m, i) => {
          const h = Math.max(((m.score || 0) / 5) * 28, 3);
          return <div key={i} className="flex-1 rounded-sm" style={{height: h, background: healthColor(m.score, 0.7)}} title={`${m.date}: ${m.score != null ? m.score.toFixed(1) : "—"}`}/>;
        })}
      </div>

      <div className="mt-3 pt-3 border-t flex items-center justify-between text-[12px]" style={{borderColor: RULE}}>
        <span style={{color: SUBINK}}>Last: {last.date} · {last.score != null ? last.score.toFixed(1) : "—"}</span>
        <span className="inline-flex items-center gap-1 font-medium transition-all group-hover:gap-1.5" style={{color: INK}}>
          Open<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M6 3l5 5-5 5" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </span>
      </div>
    </button>
  );
}

function MeetingTrendChart({ data, width = 580, height = 210 }) {
  const padL = 38, padR = 18, padT = 20, padB = 30;
  const w = width - padL - padR, h = height - padT - padB;
  const min = 1, max = 5;
  const xs = i => padL + (i / (data.length - 1 || 1)) * w;
  const ys = v => padT + h - ((v - min) / (max - min)) * h;
  const validData = data.filter(d => d.score != null);
  const last = validData.length ? validData[validData.length - 1] : null;
  const path = validData.map((d, i) => {
    const origI = data.indexOf(d);
    return `${i ? "L" : "M"}${xs(origI)},${ys(d.score)}`;
  }).join(" ");
  if (!validData.length) return <div style={{height, display:"flex",alignItems:"center",justifyContent:"center",color:"oklch(0.7 0.01 250)",fontSize:13}}>No meeting data yet</div>;
  return (
    <svg width={width} height={height} className="block">
      {[1,2,3,4,5].map(v => (
        <g key={v}>
          <line x1={padL} x2={width - padR} y1={ys(v)} y2={ys(v)} stroke={RULE} strokeDasharray={v === 3 ? "" : "2 4"}/>
          <text x={padL - 8} y={ys(v) + 4} fontSize="10" textAnchor="end" fill={SUBINK} fontFamily="JetBrains Mono, monospace">{v}</text>
        </g>
      ))}
      {last && <path d={path + ` L${xs(data.indexOf(last))},${padT+h} L${xs(data.indexOf(validData[0]))},${padT+h} Z`} fill={healthColor(last.score, 0.13)}/>}
      <path d={path} fill="none" stroke={last ? healthColor(last.score) : RULE} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
      {validData.map((d, i) => {
        const origI = data.indexOf(d);
        return (
          <g key={i}>
            <circle cx={xs(origI)} cy={ys(d.score)} r="3" fill={healthColor(d.score)} stroke="white" strokeWidth="1.5"/>
            <text x={xs(origI)} y={height - 10} fontSize="10" textAnchor="middle" fill={SUBINK} fontFamily="JetBrains Mono, monospace">{d.label}</text>
          </g>
        );
      })}
    </svg>
  );
}

// Round helper (data.js' r1 not exported)
function r1(x) { return Math.round(x * 10) / 10; }

// ============================================================
// PROJECT DETAIL — Health
// ============================================================
function HealthProjectDetail({ team, data, onBack }) {
  const { dimensions } = data;
  const tAvg = avg(team.dimensions.map(d => d.current));
  const sampleResponses = OPEN_RESPONSES_HEALTH.slice(0, 6);

  return (
    <div className="space-y-7">
      <button onClick={onBack} className="inline-flex items-center gap-2 text-[13px] hover:opacity-70 transition" style={{color: SUBINK}}>
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M10 3l-5 5 5 5" strokeLinecap="round" strokeLinejoin="round"/></svg>
        Back to Team Health
      </button>

      <header>
        <div className="text-[11px] tracking-[0.2em] uppercase" style={{color: "oklch(0.45 0.15 270)"}}>{team.code} · {team.region} · {team.sector}</div>
        <h1 className="text-[34px] tracking-[-0.02em] font-semibold mt-1.5" style={{color: INK}}>{team.name}</h1>
        <p className="text-[15px] mt-1.5" style={{color: SUBINK}}>{team.size} members · {team.phase}</p>
      </header>

      {/* Pre / Post baseline pair */}
      <div className="grid grid-cols-2 gap-4">
        <ScoreTile label="Baseline Health" value={team.preDone && team.preScore != null ? team.preScore.toFixed(1) : "—"} sub={team.preDone ? "Pre survey complete" : "Pre survey pending"} accent={team.preDone ? healthColor(team.preScore) : SUBINK} highlight/>
        <ScoreTile label="Post Health" value={team.postDone && team.postScore != null ? team.postScore.toFixed(1) : "—"} sub={team.postDone ? "Post survey complete" : "Post survey pending"} accent={team.postDone ? healthColor(team.postScore) : SUBINK}/>
      </div>

      {/* Trend + radar */}
      <div className="grid grid-cols-12 gap-5">
        <Card className="col-span-8">
          <CardHeader title="Weekly trend" subtitle="6-week pulse history · 1–5 Likert · cohort average shown for comparison"/>
          <CardBody>
            <div className="overflow-x-auto">
              <TrendLine
                data={team.weekly.map(w => ({...w, label: `W${w.week}`}))}
                compare={data.cohortWeekly.map(w => ({...w, label: `W${w.week}`}))}
                compareLabel="Cohort avg"
                width={680} height={220}/>
            </div>
          </CardBody>
        </Card>
        <Card className="col-span-4">
          <CardHeader title="6-dimension radar" subtitle="Current values"/>
          <CardBody className="flex items-center justify-center"><Radar dimensions={team.dimensions} size={260}/></CardBody>
        </Card>
      </div>

      {/* Per-dimension breakdown */}
      <Card>
        <CardHeader title="Construct breakdown" subtitle="Dimension means with item-level scores"/>
        <CardBody>
          <div className="grid grid-cols-2 gap-x-6 gap-y-4">
            {team.dimensions.map(d => (
              <div key={d.id} className="border-b pb-3 last:border-b-0" style={{borderColor: RULE}}>
                <div className="flex items-baseline justify-between gap-3">
                  <div>
                    <div className="text-[10px] font-mono tracking-[0.14em] uppercase" style={{color: SUBINK}}>{d.short}</div>
                    <div className="text-[14px] tracking-tight font-medium mt-0.5" style={{color: INK}}>{d.label}</div>
                  </div>
                  <div className="text-[24px] font-semibold tracking-tight tabular-nums" style={{color: healthColor(d.current)}}>{d.current != null ? d.current.toFixed(1) : "—"}</div>
                </div>
                <p className="text-[12px] mt-1.5 leading-snug" style={{color: SUBINK}}>{d.blurb}</p>
                <div className="mt-2.5 flex gap-1">
                  {d.questions.map((q, qi) => (
                    <div key={qi} className="flex-1 h-1.5 rounded-full overflow-hidden" style={{background: "oklch(0.93 0.005 80)"}} title={`Q${qi+1}: ${q != null ? q.toFixed(1) : "—"}`}>
                      <div className="h-full" style={{width: `${(q/5)*100}%`, background: healthColor(q, 0.85)}}/>
                    </div>
                  ))}
                </div>
                <div className="text-[10.5px] font-mono mt-1 flex justify-between" style={{color: SUBINK}}>
                  <span>{d.questionRange}</span><span>{d.items} items</span>
                </div>
              </div>
            ))}
          </div>
        </CardBody>
      </Card>

      {/* Open-ended for this team */}
      <Card>
        <CardHeader title="Open-ended responses" subtitle={`Qualitative responses from ${team.short} · anonymized`} eyebrow="Live data"/>
        <OpenEndedPanel responses={(team.themes||[]).map(text => ({text, q: "What would you keep or change?", sentiment: 0}))}/>
      </Card>
    </div>
  );
}

// ============================================================
// PROJECT DETAIL — Meeting
// ============================================================
function MeetingProjectDetail({ team, data, onBack }) {
  const meetings = team.meetings;
  const totalResp = meetings.reduce((s, m) => s + m.responses, 0);
  const totalAtt = meetings.reduce((s, m) => s + m.attendees, 0);
  const respRate = totalAtt ? totalResp / totalAtt : 0;

  return (
    <div className="space-y-7">
      <button onClick={onBack} className="inline-flex items-center gap-2 text-[13px] hover:opacity-70 transition" style={{color: SUBINK}}>
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M10 3l-5 5 5 5" strokeLinecap="round" strokeLinejoin="round"/></svg>
        Back to Meeting Effectiveness
      </button>

      <header>
        <div className="text-[11px] tracking-[0.2em] uppercase" style={{color: "oklch(0.45 0.15 270)"}}>{team.code} · {team.region} · {team.sector}</div>
        <h1 className="text-[34px] tracking-[-0.02em] font-semibold mt-1.5" style={{color: INK}}>{team.name}</h1>
        <p className="text-[15px] mt-1.5" style={{color: SUBINK}}>{meetings.length} meetings logged · {totalResp} of {totalAtt} attendees responded ({Math.round(respRate*100)}%)</p>
      </header>

      <div className="grid grid-cols-4 gap-3">
        <BigStat label="Meeting average" value={team.meetingAvg != null ? team.meetingAvg.toFixed(1) : "—"} sub="across all meetings" accent={healthColor(team.meetingAvg)}/>
        <BigStat label="Best meeting" value={meetings.length ? Math.max(...meetings.map(m=>m.score)).toFixed(1) : "—"} sub={(() => { const m = meetings.reduce((a,b)=>b.score>a.score?b:a); return m.date; })()}/>
        <BigStat label="Weakest meeting" value={meetings.length ? Math.min(...meetings.map(m=>m.score)).toFixed(1) : "—"} sub={(() => { const m = meetings.reduce((a,b)=>b.score<a.score?b:a); return m.date; })()}/>
        <BigStat label="Response rate" value={`${Math.round(respRate*100)}%`} sub={`${totalResp} of ${totalAtt}`}/>
      </div>

      <Card>
        <CardHeader title="Meeting-by-meeting breakdown" subtitle="Last 8 meetings · score and attendance"/>
        <div>
          <div className="grid grid-cols-12 gap-3 text-[10.5px] tracking-[0.14em] uppercase px-6 py-3 border-b" style={{borderColor: RULE, background: PAPER_2, color: SUBINK}}>
            <div className="col-span-3">Date</div>
            <div className="col-span-3">Attendees</div>
            <div className="col-span-2">Responses</div>
            <div className="col-span-2 text-right">Rate</div>
            <div className="col-span-2 text-right">Score</div>
          </div>
          {meetings.map(m => {
            const rate = m.attendees ? m.responses / m.attendees : 0;
            return (
              <div key={m.id} className="grid grid-cols-12 gap-3 px-6 py-3 border-b last:border-b-0 text-[13px]" style={{borderColor: RULE}}>
                <div className="col-span-3 font-mono tabular-nums" style={{color: INK}}>{m.date}</div>
                <div className="col-span-3 font-mono tabular-nums" style={{color: INK}}>{m.attendees}</div>
                <div className="col-span-2 font-mono tabular-nums" style={{color: INK}}>{m.responses}</div>
                <div className="col-span-2 text-right font-mono tabular-nums" style={{color: SUBINK}}>{Math.round(rate*100)}%</div>
                <div className="col-span-2 text-right">
                  <span className="inline-flex items-center gap-2 px-2.5 py-0.5 rounded-md font-mono tabular-nums text-[12.5px]" style={{background: healthColor(m.score, 0.18), color: healthColor(m.score, 0.95)}}>{m.score != null ? m.score.toFixed(1) : "—"}</span>
                </div>
              </div>
            );
          })}
        </div>
      </Card>

      <Card>
        <CardHeader title="Item-level breakdown" subtitle="12 questions · this team's averages"/>
        <CardBody>
          <div className="grid grid-cols-2 gap-x-6 gap-y-2.5">
            {team.meetingItems.map((it, i) => (
              <div key={i} className="flex items-center gap-3">
                <div className="text-[12.5px] flex-1 leading-snug" style={{color: INK}}>{it.q}</div>
                <Bar value={it.score} width={100} height={6}/>
                <div className="text-[12px] font-mono tabular-nums w-9 text-right" style={{color: healthColor(it.score)}}>{it.score != null ? it.score.toFixed(1) : '—'}</div>
              </div>
            ))}
          </div>
        </CardBody>
      </Card>

      <Card>
        <CardHeader title="Open-ended responses" subtitle={`Meeting feedback from ${team.short} · anonymized`} eyebrow="Live data"/>
        <OpenEndedPanel responses={(team.themes||[]).map(text => ({text, q: "What would you keep or change?", sentiment: 0}))}/>
      </Card>
    </div>
  );
}

// ============================================================
// SHARED COMPONENTS
// ============================================================

function ScoreTile({ label, value, sub, accent, highlight }) {
  return (
    <div className="rounded-2xl border p-6" style={{borderColor: RULE, background: highlight ? "white" : PAPER_2, boxShadow: highlight ? "0 4px 16px rgba(0,0,0,0.04)" : "none"}}>
      <div className="text-[11px] tracking-[0.18em] uppercase" style={{color: SUBINK}}>{label}</div>
      <div className="mt-3 text-[42px] tracking-[-0.025em] font-semibold tabular-nums leading-none" style={{color: accent}}>{value}</div>
      <div className="mt-2 text-[12.5px]" style={{color: SUBINK}}>{sub}</div>
    </div>
  );
}

function BigStat({ label, value, sub, accent }) {
  return (
    <div className="rounded-2xl p-4 bg-white border" style={{borderColor: RULE}}>
      <div className="text-[11px] tracking-[0.16em] uppercase" style={{color: SUBINK}}>{label}</div>
      <div className="mt-2.5 text-[26px] tracking-[-0.02em] font-semibold tabular-nums leading-none truncate" style={{color: accent || INK}}>{value}</div>
      <div className="mt-1.5 text-[11px] leading-snug truncate" style={{color: SUBINK}}>{sub}</div>
    </div>
  );
}

function KV({ label, v }) {
  return <div><span className="uppercase tracking-wider text-[11px]" style={{color: SUBINK}}>{label}</span><span className="ml-2 tabular-nums" style={{color: INK}}>{v}</span></div>;
}

function RiskRing({ summary, teams, onSelectTeam }) {
  const total = teams.length;
  const arc = (start, frac, color) => {
    if (frac <= 0) return null;
    const r = 56, cx = 70, cy = 70;
    const a0 = -Math.PI/2 + start*2*Math.PI;
    const a1 = a0 + frac*2*Math.PI;
    const large = frac > 0.5 ? 1 : 0;
    const x0 = cx + r*Math.cos(a0), y0 = cy + r*Math.sin(a0);
    const x1 = cx + r*Math.cos(a1), y1 = cy + r*Math.sin(a1);
    return <path d={`M${x0},${y0} A${r},${r} 0 ${large} 1 ${x1},${y1}`} stroke={color} strokeWidth="13" fill="none" strokeLinecap="butt"/>;
  };
  const c = summary.riskCounts;
  const fH = c.healthy/total, fW = c.watch/total, fC = c.critical/total;
  return (
    <div className="flex items-center gap-5">
      <svg width="140" height="140">
        <circle cx="70" cy="70" r="56" stroke="oklch(0.93 0.005 80)" strokeWidth="13" fill="none"/>
        {arc(0, fH, riskColor("healthy"))}
        {arc(fH, fW, riskColor("watch"))}
        {arc(fH+fW, fC, riskColor("critical"))}
        <text x="70" y="68" textAnchor="middle" fontSize="26" fill={INK} fontFamily="JetBrains Mono, monospace">{total}</text>
        <text x="70" y="86" textAnchor="middle" fontSize="9" fill={SUBINK} fontFamily="JetBrains Mono, monospace" letterSpacing="0.18em">TEAMS</text>
      </svg>
      <div className="space-y-2 text-[13px] flex-1 min-w-0">
        <RiskRow label="Healthy" count={c.healthy} total={total} color={riskColor("healthy")}/>
        <RiskRow label="Watch" count={c.watch} total={total} color={riskColor("watch")}/>
        <RiskRow label="Critical" count={c.critical} total={total} color={riskColor("critical")}/>
        <div className="pt-2.5 border-t mt-2.5 space-y-1" style={{borderColor: RULE}}>
          {teams.filter(t=>t.risk!=="healthy").slice(0,3).map(t => (
            <button key={t.id} onClick={()=>onSelectTeam(t.id)} className="w-full text-left text-[12px] flex items-center gap-2 hover:bg-stone-50 px-2 py-1 rounded-lg" style={{color: SUBINK}}>
              <span className="size-1.5 rounded-full shrink-0" style={{background: riskColor(t.risk)}}/>
              <span className="truncate flex-1">{t.short}</span>
              <span className="font-mono tabular-nums shrink-0" style={{color: healthColor(t.current)}}>{t.current != null ? t.current.toFixed(1) : "—"}</span>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

function RiskRow({label, count, total, color}) {
  const pct = (count/total)*100;
  return (
    <div className="flex items-center gap-2">
      <span className="size-2 rounded-full shrink-0" style={{background: color}}/>
      <span style={{color: INK}} className="w-16 shrink-0">{label}</span>
      <div className="flex-1 h-1 rounded-full overflow-hidden" style={{background: "oklch(0.93 0.005 80)"}}>
        <div className="h-full rounded-full" style={{width: `${pct}%`, background: color}}/>
      </div>
      <span className="font-mono tabular-nums w-7 text-right shrink-0" style={{color: INK}}>{count}</span>
    </div>
  );
}

// Open-ended panel — sentiment-coded responses with theme grouping
function OpenEndedPanel({ responses, byTheme }) {
  const [filter, setFilter] = useStateRC("all");
  const filtered = filter === "all" ? responses
    : filter === "pos" ? responses.filter(r => r.sentiment > 0)
    : filter === "neg" ? responses.filter(r => r.sentiment < 0)
    : responses.filter(r => r.sentiment === 0);

  const counts = {
    all: responses.length,
    pos: responses.filter(r => r.sentiment > 0).length,
    neg: responses.filter(r => r.sentiment < 0).length,
    neu: responses.filter(r => r.sentiment === 0).length,
  };

  return (
    <div>
      <div className="px-6 pt-4 pb-3 border-b flex items-center gap-2 flex-wrap" style={{borderColor: RULE}}>
        <Segmented value={filter} onChange={setFilter}
          options={[
            { v: "all", label: `All ${counts.all}` },
            { v: "pos", label: `Positive ${counts.pos}` },
            { v: "neg", label: `Concerns ${counts.neg}` },
            ...(counts.neu ? [{ v: "neu", label: `Neutral ${counts.neu}` }] : []),
          ]}/>
        <span className="text-[11px] ml-auto" style={{color: SUBINK}}>Anonymized · open-text · coded by theme & sentiment</span>
      </div>
      <div className="divide-y" style={{borderColor: RULE}}>
        {filtered.map((r, i) => (
          <div key={i} className="px-6 py-4 flex items-start gap-4">
            <div className="shrink-0 mt-0.5">
              <span className="size-2 rounded-full inline-block" style={{background: r.sentiment > 0 ? "oklch(0.62 0.10 145)" : r.sentiment < 0 ? "oklch(0.58 0.15 28)" : SUBINK}}/>
            </div>
            <div className="flex-1 min-w-0">
              <div className="flex items-baseline gap-2 flex-wrap">
                {byTheme && r.theme && <span className="text-[10px] font-mono tracking-[0.14em] uppercase px-1.5 py-0.5 rounded" style={{background: PAPER_2, color: SUBINK}}>{r.theme}</span>}
                <span className="text-[11.5px] italic" style={{color: SUBINK, fontFamily: "Newsreader, serif"}}>{r.q}</span>
              </div>
              <p className="text-[14px] mt-1.5 leading-relaxed" style={{color: INK, fontFamily: "Newsreader, serif"}}>"{r.text}"</p>
            </div>
            <div className="shrink-0 text-[10.5px] font-mono tracking-[0.1em] uppercase" style={{color: r.sentiment > 0 ? "oklch(0.42 0.10 145)" : r.sentiment < 0 ? "oklch(0.5 0.13 28)" : SUBINK}}>
              {r.sentiment > 0 ? "Positive" : r.sentiment < 0 ? "Concern" : "Neutral"}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Response activity log — used in both health and meeting tabs
function ResponseLog({ teams, kind }) {
  const all = [];
  const today = new Date();
  teams.forEach((t, ti) => {
    const lastWk = t.weekly[t.weekly.length - 1];
    const n = Math.min(lastWk.responses, 3);
    for (let i = 0; i < n; i++) {
      const minsAgo = (ti * 13 + i * 19) % 360 + 4;
      const dt = new Date(today.getTime() - minsAgo * 60000);
      const dur = `${4 + ((t.size + i) % 4)}m ${((t.size * 7 + i * 13) % 60).toString().padStart(2,'0')}s`;
      const isMeeting = kind === "meeting" ? true : (kind === "health" ? false : i % 3 === 0);
      if (kind === "meeting" && !isMeeting) continue;
      if (kind === "health" && isMeeting && i !== 0) continue;
      all.push({
        id: `${t.id}-${i}-${kind}`,
        team: t,
        instrument: isMeeting ? "Meeting Effectiveness" : "Team Health",
        wave: `Wk ${lastWk.week}`,
        submitted: dt,
        duration: dur,
        score: lastWk.score,
        complete: i % 6 !== 5,
        minsAgo,
      });
    }
  });
  all.sort((a, b) => b.submitted - a.submitted);
  const rows = all.slice(0, 15);
  const fmtAgo = (m) => m < 60 ? `${m}m ago` : `${Math.floor(m/60)}h ${m%60}m ago`;
  const fmtTime = (d) => d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });

  return (
    <div>
      <div className="grid grid-cols-12 gap-3 text-[10.5px] tracking-[0.12em] uppercase px-6 py-3 border-b" style={{borderColor: RULE, background: PAPER_2, color: SUBINK}}>
        <div className="col-span-3">Project</div>
        <div className="col-span-2">Instrument</div>
        <div className="col-span-1">Wave</div>
        <div className="col-span-2">Submitted</div>
        <div className="col-span-1 text-right">Dur</div>
        <div className="col-span-1 text-right">Score</div>
        <div className="col-span-2 text-right">Status</div>
      </div>
      {rows.map(r => (
        <div key={r.id} className="grid grid-cols-12 gap-3 px-6 py-2.5 border-b last:border-b-0 text-[12.5px] hover:bg-stone-50/60" style={{borderColor: RULE}}>
          <div className="col-span-3 truncate" style={{color: INK}}>{r.team.short}</div>
          <div className="col-span-2 truncate" style={{color: SUBINK}}>{r.instrument}</div>
          <div className="col-span-1 font-mono" style={{color: INK}}>{r.wave}</div>
          <div className="col-span-2 font-mono tabular-nums" style={{color: SUBINK}}>{fmtTime(r.submitted)} · {fmtAgo(r.minsAgo)}</div>
          <div className="col-span-1 text-right font-mono tabular-nums" style={{color: SUBINK}}>{r.duration}</div>
          <div className="col-span-1 text-right font-mono tabular-nums" style={{color: healthColor(r.score)}}>{r.score != null ? r.score.toFixed(1) : "—"}</div>
          <div className="col-span-2 text-right">{r.complete ? <Pill color="healthy">Complete</Pill> : <Pill color="watch">Partial</Pill>}</div>
        </div>
      ))}
    </div>
  );
}

Object.assign(window, { ResearcherView });
