/* ============================================================
   分析 — Sales CRM のデータ分析（月／週／日／期間指定）。業務週会向け。
   ・期間：月/週/日トグル＋「期間指定」で任意の from〜to
   ・担当者パフォーマンス：案件数/成約/成約率/提案書提出/平均商談回数/得意段階
   ・チャート（Chart.js）：担当者別ドーナツ・商談回数段階別バー・ランク分布ドーナツ
   ・AI総評：プロ営業マネージャーAI（server /api/analytics/summary）
   集計はストアの cases / users から（期間内に商談日が1つでもある案件＝期間の対象。跨月案件は両方の月に出る）。
   ※会議ごとの担当者は未記録のため、商談はすべて案件のメイン担当に帰属して集計。
   ============================================================ */

/* Chart.js のライフサイクル管理（canvas を React で安全に使う） */
function ChartCanvas({ type, data, options, height }) {
  const ref = React.useRef(null);
  const inst = React.useRef(null);
  const key = JSON.stringify({ type, data, options });
  React.useEffect(() => {
    if (!ref.current || !window.Chart) return;
    if (inst.current) { inst.current.destroy(); inst.current = null; }
    inst.current = new window.Chart(ref.current, { type, data, options });
    return () => { if (inst.current) { inst.current.destroy(); inst.current = null; } };
  }, [key]);
  if (!window.Chart) return <div style={{ height: height || 220, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#9aa1ab', fontSize: 12.5 }}>{t('an.chartLoading')}</div>;
  return <div style={{ height: height || 220, position: 'relative' }}><canvas ref={ref} /></div>;
}

/* 失注分析（AI）：失注案件（status==='lost'）を横断し、負けた理由の共通テーマ＋改善策をAIが抽出。
   各案件の直近の商談メモ・顧客の反応・備考を「経緯」として送る（社名等はサーバに渡すが表示は集約）。 */
function LossAnalysisCard() {
  const { cases, meetings, showToast } = useStore();
  const D = window.APP_DATA;
  const [busy, setBusy] = React.useState(false);
  const [res, setRes] = React.useState(null);
  const lostCases = cases.filter(c => c.status === 'lost');
  // 金額（見積書明細の合計）。caseAmount は AnalyticsScreen 内のローカルなので、ここでは自前で計算
  const amountOf = (c) => (((c.quoteSheet && c.quoteSheet.items) || []).reduce((s, it) => it.type === 'text' ? s : s + (Number(it.qty) || 0) * (Number(it.unitPrice) || 0), 0));
  const gen = async () => {
    if (busy) return;
    if (!lostCases.length) { showToast(t('an.loss.none'), 'x'); return; }
    setBusy(true);
    try {
      const items = lostCases.slice(0, 80).map(c => {
        const cust = D.customer(c.customerId) || {};
        const ms = (meetings || []).filter(m => m.caseId === c.id).slice().sort((a, b) => String(b.datetime || '').localeCompare(String(a.datetime || '')));
        const fb = ms.map(m => m.customerFeedback).filter(Boolean)[0] || '';
        const sum = ms.map(m => m.summary).filter(Boolean)[0] || '';
        const reason = [lostReasonLabel(c.lostReason), c.lostReasonNote].filter(Boolean).join('：'); // 手入力の失注理由を最優先の材料に
        return { title: c.title, company: cust.company || '', industry: cust.industry || '', categories: c.categories || [], amount: amountOf(c), context: [reason ? '失注理由:' + reason : null, sum, fb, c.note].filter(Boolean).join(' / ').slice(0, 500) };
      });
      const r = await API.lossAnalysis(items);
      setRes(r.analysis || null);
    } catch (e) { showToast(t('an.loss.failed', { msg: e.message || '' }), 'x'); }
    setBusy(false);
  };
  return (
    <Card title={t('an.loss.title') + '（' + lostCases.length + '）'} style={{ marginBottom: 16 }}
      action={<Button variant="primary" size="sm" icon="spark" onClick={gen} disabled={busy || !lostCases.length}>{busy ? t('an.aiReview.generating') : (res ? t('an.aiReview.regenerate') : t('an.loss.generate'))}</Button>}>
      {!res
        ? <div style={{ fontSize: 12.5, color: '#9aa1ab', lineHeight: 1.7 }}>{lostCases.length ? t('an.loss.desc', { n: lostCases.length }) : t('an.loss.none')}</div>
        : <div>
            {res.summary && <div style={{ fontSize: 13, color: '#3b414b', lineHeight: 1.7, marginBottom: 12, padding: '10px 13px', background: '#f6f7fa', borderRadius: 9 }}>{res.summary}</div>}
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {(res.themes || []).map((th, i) => (
                <div key={i} style={{ border: '1px solid #f1e3e3', borderRadius: 10, padding: 12, background: '#fffafa' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ fontSize: 12, fontWeight: 800, color: '#b91c1c', background: '#fdecec', borderRadius: 6, minWidth: 22, textAlign: 'center', padding: '1px 6px' }}>{th.count}</span>
                    <span style={{ fontSize: 13.5, fontWeight: 700, color: '#1f2430' }}>{th.reason}</span>
                  </div>
                  {th.detail && <div style={{ fontSize: 12.5, color: '#6b727c', marginTop: 6, lineHeight: 1.6 }}>{th.detail}</div>}
                  {th.suggestion && <div style={{ fontSize: 12.5, color: '#15803d', marginTop: 7, lineHeight: 1.6, display: 'flex', gap: 6 }}><Icon name="spark" size={13} stroke={2} style={{ color: '#15803d', flex: '0 0 auto', marginTop: 2 }} /><span><b>{t('an.loss.fix')}</b>{th.suggestion}</span></div>}
                </div>
              ))}
            </div>
          </div>}
    </Card>
  );
}

function AnalyticsScreen() {
  const { cases, rcLeads, showToast, saveAiReport } = useStore(); // store の cases は mergedInto（統合済み重複）除外済み
  const D = window.APP_DATA;
  const [mode, setMode] = React.useState('month'); // month / week / day / range
  const [anchor, setAnchor] = React.useState(today());
  const [rs, setRs] = React.useState(today());   // 期間指定 from
  const [re, setRe] = React.useState(today());   // 期間指定 to
  const [showRange, setShowRange] = React.useState(false);
  const [ai, setAi] = React.useState('');
  const [aiBusy, setAiBusy] = React.useState(false);
  const [drill, setDrill] = React.useState(null); // 担当者表など 数字クリックの内訳モーダル {title, cases}
  const [showDetail, setShowDetail] = React.useState(false); // 詳細分析（担当者・グラフ）の折りたたみ
  const [detailTab, setDetailTab] = React.useState('owner');  // owner / dist
  const [funnelAll, setFunnelAll] = React.useState(false);    // 経由別ファネル: false=今期(期間連動) / true=累計

  const pad = (n) => String(n).padStart(2, '0');
  const fmtYmd = (x) => `${x.getFullYear()}-${pad(x.getMonth() + 1)}-${pad(x.getDate())}`;
  const [ay, am, ad] = anchor.split('-').map(Number);

  // 期間 [start, end)（YYYY-MM-DD、end は排他）
  const period = (() => {
    if (mode === 'range') {
      if (!rs || !re) return { start: '9999', end: '9999' };
      const lo = rs <= re ? rs : re, hi = rs <= re ? re : rs;
      const [hy, hm, hd] = hi.split('-').map(Number);
      return { start: lo, end: fmtYmd(new Date(hy, hm - 1, hd + 1)) };
    }
    if (mode === 'day') return { start: anchor, end: fmtYmd(new Date(ay, am - 1, ad + 1)) };
    if (mode === 'week') { const wd = (new Date(ay, am - 1, ad).getDay() + 6) % 7; return { start: fmtYmd(new Date(ay, am - 1, ad - wd)), end: fmtYmd(new Date(ay, am - 1, ad - wd + 7)) }; }
    return { start: fmtYmd(new Date(ay, am - 1, 1)), end: fmtYmd(new Date(ay, am, 1)) };
  })();
  const periodLabel = (() => {
    if (mode === 'range') return `${(rs <= re ? rs : re).replace(/-/g, '/')} 〜 ${(rs <= re ? re : rs).replace(/-/g, '/')}`;
    if (mode === 'day') return `${ay}年${am}月${ad}日`;
    if (mode === 'week') { const e = period.end.split('-').map(Number); return `${period.start.replace(/-/g, '/')} 〜 ${fmtYmd(new Date(e[0], e[1] - 1, e[2] - 1)).replace(/-/g, '/')}`; }
    return `${ay}年${am}月`;
  })();
  const shift = (dir) => {
    if (mode === 'range') return;
    if (mode === 'day') setAnchor(fmtYmd(new Date(ay, am - 1, ad + dir)));
    else if (mode === 'week') setAnchor(fmtYmd(new Date(ay, am - 1, ad + dir * 7)));
    else setAnchor(fmtYmd(new Date(ay, am - 1 + dir, 1)));
  };

  const inP = (ymd) => !!ymd && ymd >= period.start && ymd < period.end;
  /* 期間内の案件＝「期間内に商談日が1つでもある」案件。6月→7月と商談が続く案件は両方の月に出る。
     旧実装は caseMeetingAt（単一代表日＝直近の未来、無ければ最後の商談日）で判定していたため、
     次回商談が入るたびに代表日が翌月へ移動し、過去月の件数が遡って減っていた
     （2026-07-18 指摘：6月に45件あったはずが27件表示に。root cause の修正）。 */
  const caseDays = (c) => caseMeetingDates(c).map(d => (d || '').slice(0, 10));
  const periodCases = cases.filter(c => caseDays(c).some(inP));
  const total = periodCases.length;
  /* 期間終了時点での実施済み商談回数（日単位・重複除外）。段階別（1回目/2回目…）の分類は
     全期間の caseMeetingCount ではなくこれを使う＝過去月を見た時に「その後の商談」で
     段階が繰り上がって数字が変わるのを防ぐ（件数の修正と同じ遡及ドリフト対策） */
  const heldCountTo = (c, endEx) => new Set(caseDays(c).filter(d => d && d <= today() && d < endEx)).size;

  // ランク→温度感スコア（A=高〜E=低を 100〜0 に均等割当）。'未'(未設定)は除外＝温度感に含めない
  const realRanks = D.RANK_ORDER.filter(r => r !== '未');
  const N = realRanks.length;
  const rankScore = {}; realRanks.forEach((r, i) => { rankScore[r] = N > 1 ? Math.round((1 - i / (N - 1)) * 100) : 100; });
  // 担当者別パフォーマンス（メイン担当に帰属）
  const oStat = {};
  periodCases.forEach(c => {
    const k = c.ownerId || '_none';
    const s = oStat[k] || (oStat[k] = { n: 0, won: 0, submitted: 0, mtgSum: 0, st: { 1: 0, 2: 0, '3+': 0 }, ranks: {}, tempSum: 0, tempN: 0, satSum: 0, satN: 0, nCases: [], wonCases: [], submittedCases: [] });
    s.n++; s.nCases.push(c); if (isCaseWonStatus(c.status)) { s.won++; s.wonCases.push(c); } if (c.proposalStatus === 'submitted') { s.submitted++; s.submittedCases.push(c); }
    const mc = heldCountTo(c, period.end); s.mtgSum += mc; if (mc >= 3) s.st['3+']++; else if (mc >= 1) s.st[mc]++;
    const r = (c.rank && D.RANKS[c.rank]) ? c.rank : '未';
    s.ranks[r] = (s.ranks[r] || 0) + 1;
    if (rankScore[r] != null) { s.tempSum += rankScore[r]; s.tempN++; }
    // 発注者満足度（ReadyCrew 初回商談アンケート 1〜4。回答のある案件のみメイン担当に帰属）
    const sv = c.ordererReview && c.ordererReview.satisfaction;
    if (sv >= 1 && sv <= 4) { s.satSum += sv; s.satN++; }
  });
  const stageLabel = { 1: t('an.stage.first'), 2: t('an.stage.second'), '3+': t('an.stage.thirdPlus') };
  const owners = Object.keys(oStat).map(k => {
    const s = oStat[k]; const best = ['1', '2', '3+'].reduce((b, x) => s.st[x] > s.st[b] ? x : b, '1');
    return { id: k, name: k === '_none' ? t('an.unassigned') : ((D.user(k) || {}).short || (D.user(k) || {}).name || '?'),
      color: k === '_none' ? '#c4c9d0' : ((D.user(k) || {}).color || '#4a5af0'),
      n: s.n, won: s.won, submitted: s.submitted, avg: s.n ? (s.mtgSum / s.n) : 0, st: s.st, best: s.n ? stageLabel[best] : '—',
      ranks: s.ranks, temp: s.tempN ? Math.round(s.tempSum / s.tempN) : null,
      sat: s.satN ? (s.satSum / s.satN) : null, satN: s.satN,
      nCases: s.nCases, wonCases: s.wonCases, submittedCases: s.submittedCases };
  }).sort((a, b) => b.n - a.n);
  /* 担当者表の数字→内訳（会社・案件）モーダル。map内クロージャや onClick内の t 参照を避け、
     コンポーネント直下で定義（2026-07-16 owner-drillのクラッシュ対策・単位ラベルは事前計算）。 */
  const unitCnt = t('unit.count');
  const drillNum = (val, arr, label, style) => (val && arr && arr.length)
    ? <button onClick={() => setDrill({ title: label + '（' + arr.length + unitCnt + '）', cases: arr })}
        style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: 'inherit', fontWeight: 'inherit', color: 'inherit', textDecoration: 'underline', textDecorationColor: '#d3d7de', textUnderlineOffset: 3, padding: 0, ...(style || {}) }}>{val}</button>
    : <span style={style}>{val}</span>;
  const tempColor = (t) => t == null ? '#c4c9d0' : t >= 70 ? '#16a34a' : t >= 50 ? '#d97706' : '#6b7280';
  // 発注者満足度（1〜4）。色は 4=とてもよかった〜1=よくなかった に対応
  const SAT_LABELS = { 1: t('cd.sat.1'), 2: t('cd.sat.2'), 3: t('cd.sat.3'), 4: t('cd.sat.4') }; // ラベルは案件詳細/一覧と統一（cd.sat.*）
  const SAT_COLORS = { 1: '#dc2626', 2: '#d97706', 3: '#5b9e6f', 4: '#16a34a' };
  const satColor = (v) => v == null ? '#c4c9d0' : v >= 3.5 ? '#16a34a' : v >= 2.5 ? '#5b9e6f' : v >= 1.5 ? '#d97706' : '#dc2626';
  // 全体の満足度分布（回答のある案件のみ）
  const satDist = { 1: 0, 2: 0, 3: 0, 4: 0 }; let satTotalN = 0, satTotalSum = 0;
  periodCases.forEach(c => { const v = c.ordererReview && c.ordererReview.satisfaction; if (v >= 1 && v <= 4) { satDist[v]++; satTotalN++; satTotalSum += v; } });
  const satAvg = satTotalN ? satTotalSum / satTotalN : null;
  const satOwners = owners.filter(o => o.satN > 0);

  // 商談回数段階別・ランク分布・提案/成約
  const mtgDist = { 1: 0, 2: 0, 3: 0, '4+': 0 };
  periodCases.forEach(c => { const n = heldCountTo(c, period.end); if (n >= 4) mtgDist['4+']++; else if (n >= 1) mtgDist[n]++; });
  const rankList = D.RANK_ORDER.includes('未') ? [...D.RANK_ORDER] : [...D.RANK_ORDER, '未'];
  const rankDist = {}; rankList.forEach(r => rankDist[r] = 0);
  periodCases.forEach(c => { const r = (c.rank && D.RANKS[c.rank]) ? c.rank : '未'; rankDist[r] = (rankDist[r] || 0) + 1; });
  const submitted = periodCases.filter(c => c.proposalStatus === 'submitted').length;
  const won = periodCases.filter(c => isCaseWonStatus(c.status)).length;

  // 総合評価（全体）：案件ステータス分布・商談の進展（2回目/3回目への到達）
  const statusList = D.STATUS_ORDER && D.STATUS_ORDER.length ? D.STATUS_ORDER : Object.keys(D.STATUS || {});
  const statusDist = {}; statusList.forEach(s => statusDist[s] = 0);
  periodCases.forEach(c => { if (statusDist[c.status] != null) statusDist[c.status]++; });
  const with1mtg = mtgDist[1] + mtgDist[2] + mtgDist[3] + mtgDist['4+'];
  const reached2 = mtgDist[2] + mtgDist[3] + mtgDist['4+'];
  const reached3 = mtgDist[3] + mtgDist['4+'];
  const advanceRate = with1mtg ? Math.round(reached2 / with1mtg * 100) : 0;

  // Chart データ
  const ownerDonut = { labels: owners.map(o => o.name), datasets: [{ data: owners.map(o => o.n), backgroundColor: owners.map(o => o.color), borderWidth: 2, borderColor: '#fff' }] };
  const mtgBar = { labels: [t('an.meeting.1st'), t('an.meeting.2nd'), t('an.meeting.3rd'), t('an.meeting.4plus')], datasets: [{ label: t('an.chart.caseCount'), data: [mtgDist[1], mtgDist[2], mtgDist[3], mtgDist['4+']], backgroundColor: '#4a5af0', borderRadius: 6, maxBarThickness: 46 }] };
  const rankDonut = { labels: rankList.map(r => r === '未' ? t('an.rankUnset') : r), datasets: [{ data: rankList.map(r => rankDist[r]), backgroundColor: rankList.map(r => (D.RANKS[r] || {}).color || '#c4c9d0'), borderWidth: 2, borderColor: '#fff' }] };
  const donutOpts = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { boxWidth: 12, font: { size: 12 } } } }, cutout: '58%' };
  const barOpts = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } };
  // 担当者別の商談温度感（ランク構成の積み上げ）
  const tempStack = { labels: owners.map(o => o.name), datasets: D.RANK_ORDER.map(r => ({ label: r, data: owners.map(o => o.ranks[r] || 0), backgroundColor: (D.RANKS[r] || {}).color || '#c4c9d0', stack: 'rank', borderRadius: 3, maxBarThickness: 54 })) };
  const stackOpts = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 12 } } } }, scales: { x: { stacked: true }, y: { stacked: true, beginAtZero: true, ticks: { precision: 0 } } } };
  // 担当者別の発注者満足度（平均 1〜4・回答のある担当のみ）
  const satBar = { labels: satOwners.map(o => o.name), datasets: [{ label: t('an.chart.avgSatisfaction'), data: satOwners.map(o => +o.sat.toFixed(2)), backgroundColor: satOwners.map(o => satColor(o.sat)), borderRadius: 6, maxBarThickness: 54 }] };
  const satBarOpts = { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false },
    tooltip: { callbacks: { label: (ctx) => { const o = satOwners[ctx.dataIndex]; return t('an.chart.satTooltip', { avg: ctx.parsed.y.toFixed(1), n: o.satN }); } } } },
    scales: { y: { beginAtZero: true, max: 4, ticks: { stepSize: 1, callback: (v) => v + (SAT_LABELS[v] ? '　' + SAT_LABELS[v] : '') } } } };
  // 発注者満足度の分布（全体・回答のある案件）
  const satDonut = { labels: [1, 2, 3, 4].map(v => SAT_LABELS[v]), datasets: [{ data: [1, 2, 3, 4].map(v => satDist[v]), backgroundColor: [1, 2, 3, 4].map(v => SAT_COLORS[v]), borderWidth: 2, borderColor: '#fff' }] };

  /* ===== 追加分析：金額・コンバージョン率・前期比・経由別ファネル ===== */
  const numOf = (v) => Number(String(v == null ? '' : v).replace(/[^\d.-]/g, '')) || 0;
  // 案件の見積金額（税抜・quoteSheet の明細合計）
  const caseAmount = (c) => (((c.quoteSheet && c.quoteSheet.items) || []).reduce((s, it) => it.type === 'text' ? s : s + numOf(it.qty) * numOf(it.unitPrice), 0));
  const yen = (n) => { n = Math.round(n || 0); if (!n) return '¥0'; if (n >= 1e8) return '¥' + (n / 1e8).toFixed(n % 1e8 ? 1 : 0) + '億'; if (n >= 1e4) return '¥' + Math.round(n / 1e4).toLocaleString() + '万'; return '¥' + n.toLocaleString(); };
  const isClosed = (c) => { const st = D.STATUS && D.STATUS[c.status]; return (st && st.closed != null) ? !!st.closed : ['won', 'lost', 'done'].includes(c.status); };
  // パイプライン金額（今期・未クローズ案件の見積合計）
  const pipelineAmount = periodCases.filter(c => !isClosed(c)).reduce((s, c) => s + caseAmount(c), 0);
  /* 成約カード・成約金額＝「この期間中に成約した件数/金額」（成約日ベース・2026-07-15 ユーザー指定）。
     成約＝成約(won)＋受注(done) の両方（isCaseWonStatus・2026-07-16 受注が漏れる不具合の修正）。
     成約日は wonAt（記録があれば）→無ければ updatedAt で近似（caseWonYmd）。
     ※ファネルの成約率は分母と同じ母集団（商談日コホート）の成約数 won を使う＝率の整合を優先 */
  const wonAtOf = (c) => caseWonYmd(c);
  const wonCasesInP = cases.filter(c => isCaseWonStatus(c.status) && inP(wonAtOf(c)));
  const wonPeriod = wonCasesInP.length;
  const wonAmount = wonCasesInP.reduce((s, c) => s + caseAmount(c), 0);
  // コンバージョン率
  const withMtg = periodCases.filter(c => heldCountTo(c, period.end) >= 1).length;
  const convProposal = submitted ? Math.round(won / submitted * 100) : 0; // 提案→成約
  const convMtg = withMtg ? Math.round(won / withMtg * 100) : 0;          // 商談→成約
  const convCase = total ? Math.round(won / total * 100) : 0;            // 案件→成約
  // 前期比（同じ長さの直前期間）
  const prevPeriod = (() => { const d = (s) => { const [y, m, dd] = s.split('-').map(Number); return new Date(y, m - 1, dd); }; const st = d(period.start), en = d(period.end); const len = Math.max(1, Math.round((en - st) / 86400000)); return { start: fmtYmd(new Date(st.getFullYear(), st.getMonth(), st.getDate() - len)), end: period.start }; })();
  const inPrev = (ymd) => !!ymd && ymd >= prevPeriod.start && ymd < prevPeriod.end;
  const prevCases = cases.filter(c => caseDays(c).some(inPrev)); // 期間内判定は periodCases と同じ「商談日が1つでもある」基準
  const prevTotal = prevCases.length;
  const prevWonCases = cases.filter(c => isCaseWonStatus(c.status) && inPrev(wonAtOf(c)));
  const prevWon = prevWonCases.length;
  const prevPipeline = prevCases.filter(c => !isClosed(c)).reduce((s, c) => s + caseAmount(c), 0);
  const prevWonAmount = prevWonCases.reduce((s, c) => s + caseAmount(c), 0);
  const delta = (cur, prev) => (prev > 0 ? Math.round((cur - prev) / prev * 100) : (cur > 0 ? null : null)); // 前期0なら比率なし
  // 経由別ファネル：今期（上の期間セレクタに連動）／累計 を切替。リード→エントリー→商談化→成約
  // ・リード/エントリー … リードの取得日(registeredAt)で期間フィルタ
  // ・商談化/成約 … 案件の商談日で期間フィルタ（＝periodCases。KPI等と同じ基準）
  const caseIdSet = rcCaseIdSet(cases);
  const leadDate = (l) => (l.registeredAt || l.importedAt || '').slice(0, 10);
  const viaFunnel = (() => {
    const g = {}; const ensure = (key, meta) => g[key] || (g[key] = { meta, leads: 0, entered: 0, deals: 0, won: 0 });
    const leadPool = funnelAll ? (rcLeads || []) : (rcLeads || []).filter(l => inP(leadDate(l)));
    const casePool = funnelAll ? (cases || []) : periodCases;
    leadPool.forEach(l => { const v = apoViaMeta(l); const x = ensure(v.key, v); x.leads++; if (rcLeadEntered(l, caseIdSet)) x.entered++; });
    casePool.forEach(c => { const v = caseViaMeta(c); if (v.key === 'manual' || v.key === 'gmail') return; const x = ensure(v.key, v); x.deals++; if (isCaseWonStatus(c.status)) x.won++; });
    return Object.values(g).sort((a, b) => (b.leads + b.deals) - (a.leads + a.deals));
  })();

  const genAi = async () => {
    if (aiBusy) return; setAiBusy(true);
    try {
      const r = await API.analyticsSummary(period.start, period.end, periodLabel);
      const text = (r && r.summary) || '';
      setAi(text || t('an.ai.noComment'));
      // 生成成功時は履歴へ保存（リロードしても残る・後から調閲/削除できる）
      if (text) saveAiReport({ type: 'analytics_summary', title: periodLabel, content: text, meta: { start: period.start, end: period.end, mode, total } });
    }
    catch (e) { showToast(t('an.ai.genFailed', { msg: e.message || '' }), 'x'); }
    setAiBusy(false);
  };

  /* 営業レポート出力：この期間のKPI・成約率・ステータス構成・経由別ファネル・担当者・AI総評を
     印刷用1ページに整形（実績集と同方式）。経営報告・振り返り会議にそのまま使える */
  const exportReport = () => {
    const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const dTxt = (d) => (d == null ? '' : (d >= 0 ? '▲ +' + d + '%' : '▼ ' + d + '%'));
    const dCol = (d) => (d == null ? '#9aa1ab' : d >= 0 ? '#15803d' : '#dc2626');
    const kpis = [
      [window.t("an.chart.caseCount"), total + ' 件', delta(total, prevTotal)],
      [window.t("an.col.won"), wonPeriod + ' 件', delta(wonPeriod, prevWon)],
      [window.t("label.extra0"), pipelineAmount ? yen(pipelineAmount) : '—', pipelineAmount ? delta(pipelineAmount, prevPipeline) : null],
      [window.t("label.extra1"), wonAmount ? yen(wonAmount) : '—', wonAmount ? delta(wonAmount, prevWonAmount) : null],
    ];
    const convs = [[window.t("label.extra2"), convCase, won + '/' + total], [window.t("label.extra3"), convMtg, won + '/' + withMtg], [window.t("label.extra4"), convProposal, won + '/' + submitted]];
    const stTxt = statusList.filter(s => statusDist[s] > 0).map(s => esc(((D.STATUS[s] || {}).label || s)) + ' <b>' + statusDist[s] + '</b>').join('　·　');
    const fRows = viaFunnel.map(g => { const er = g.leads ? Math.round(g.entered / g.leads * 100) + '%' : '—'; const wr = g.deals ? Math.round(g.won / g.deals * 100) + '%' : '—';
      return `<tr><td>${esc(g.meta.label)}</td><td>${g.leads}</td><td>${g.entered}（${er}）</td><td>${g.deals}</td><td>${g.won}（${wr}）</td></tr>`; }).join('');
    const oRows = owners.map(o => `<tr><td>${esc(o.name)}</td><td>${o.n}</td><td>${o.won}</td><td>${o.n ? Math.round(o.won / o.n * 100) : 0}%</td><td>${o.submitted}</td><td>${o.avg.toFixed(1)}回</td><td>${o.sat != null ? o.sat.toFixed(1) + '/4' : '—'}</td></tr>`).join('');
    const now = new Date();
    const html = `<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title>営業レポート ${esc(periodLabel)}</title><style>
      body{font-family:-apple-system,BlinkMacSystemFont,'Hiragino Sans','Noto Sans JP',sans-serif;color:#1f2430;margin:0;padding:34px 42px;background:#fff}
      h1{font-size:20px;margin:0 0 2px}.sub{font-size:11px;color:#8a909b;margin-bottom:20px}
      h2{font-size:13.5px;margin:20px 0 8px;padding-left:9px;border-left:3px solid #4a5af0}
      .kpis{display:flex;gap:10px}.kpi{flex:1;border:1px solid #e6e8ec;border-radius:10px;padding:10px 13px}
      .kl{font-size:10.5px;color:#7b828d;font-weight:600}.kv{font-size:18px;font-weight:800;margin-top:2px}.kd{font-size:10.5px;font-weight:700;margin-top:2px}
      table{width:100%;border-collapse:collapse;font-size:11.5px}th{text-align:left;color:#9aa1ab;font-size:10.5px;padding:5px 8px;border-bottom:1px solid #e6e8ec}td{padding:6px 8px;border-bottom:1px solid #f2f3f5}
      .conv{display:flex;gap:10px}.cv{flex:1;border:1px solid #e6e8ec;border-radius:10px;padding:9px 13px}.cvr{font-size:17px;font-weight:800;color:#4a5af0}
      .st{font-size:12px;line-height:1.8}.ai{font-size:11.5px;line-height:1.8;white-space:pre-wrap;background:#fafbfc;border:1px solid #eef0f3;border-radius:10px;padding:12px 14px}
      .toolbar{position:fixed;top:12px;right:14px}.toolbar button{font:600 12.5px/1 -apple-system,sans-serif;padding:9px 16px;border-radius:8px;border:none;background:#4a5af0;color:#fff;cursor:pointer}
      @media print{.toolbar{display:none}body{padding:0}}
    </style></head><body>
      <div class="toolbar"><button onclick="window.print()">印刷 / PDFに保存</button></div>
      <h1>営業レポート — ${esc(periodLabel)}</h1><div class="sub">ALION Sales ／ ${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()} 出力 ・ 対象 ${total} 件（商談日ベース）</div>
      <div class="kpis">${kpis.map(k => `<div class="kpi"><div class="kl">${esc(k[0])}</div><div class="kv">${esc(k[1])}</div><div class="kd" style="color:${dCol(k[2])}">${dTxt(k[2])}<span style="color:#9aa1ab;font-weight:500"> 前期比</span></div></div>`).join('')}</div>
      <h2>成約率</h2><div class="conv">${convs.map(c => `<div class="cv"><div class="kl">${esc(c[0])}</div><div class="cvr">${c[1]}%<span style="font-size:10.5px;color:#9aa1ab;font-weight:600">　${esc(c[2])}</span></div></div>`).join('')}</div>
      <h2>案件ステータス構成</h2><div class="st">${stTxt || '—'}</div>
      <h2>経由別ファネル（${funnelAll ? '累計' : '今期'}）</h2><table><tr><th>経由</th><th>リード</th><th>エントリー（率）</th><th>商談化</th><th>成約（率）</th></tr>${fRows || '<tr><td colspan="5">データなし</td></tr>'}</table>
      <h2>担当者パフォーマンス</h2><table><tr><th>担当</th><th>案件</th><th>成約</th><th>勝率</th><th>提案提出</th><th>平均商談</th><th>満足度</th></tr>${oRows || '<tr><td colspan="7">この期間の商談はありません</td></tr>'}</table>
      <h2>商談の進展・発注者満足度</h2><div class="st">1回目 <b>${mtgDist[1] || 0}</b> → 2回目+ <b>${reached2}</b> → 3回目+ <b>${reached3}</b>（2回目以降への到達率 <b>${advanceRate}%</b>）　·　満足度平均 <b>${satTotalN ? satAvg.toFixed(1) + '/4' : '—'}</b>（回答${satTotalN}件）</div>
      ${ai ? `<h2>AI総評</h2><div class="ai">${esc(ai)}</div>` : ''}
    </body></html>`;
    const w = window.open('', '_blank');
    if (!w) { showToast(window.t("label.extra5"), 'x'); return; }
    w.document.write(html); w.document.close();
  };

  const modeBtn = (v, l) => (
    <button key={v} onClick={() => setMode(v)} style={{ padding: '5px 15px', borderRadius: 7, border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600,
      background: mode === v ? '#fff' : 'transparent', color: mode === v ? '#1c1f26' : '#7b828d', boxShadow: mode === v ? '0 1px 2px rgba(20,22,40,.1)' : 'none' }}>{l}</button>
  );
  const Bar = ({ n, max, color, label, sub }) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '6px 0' }}>
      <div style={{ width: 90, flex: '0 0 auto', fontSize: 13, color: '#3b414b', fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
      <div style={{ flex: 1, height: 20, background: '#f3f4f7', borderRadius: 6, overflow: 'hidden' }}>
        <div style={{ width: (n / max * 100) + '%', height: '100%', background: color, borderRadius: 6, minWidth: n ? 4 : 0 }} />
      </div>
      <div style={{ width: 92, flex: '0 0 auto', textAlign: 'right', fontSize: 12.5, color: '#1f2430', fontWeight: 700 }}>{n} {t('unit.count')}{sub != null ? <span style={{ color: '#9aa1ab', fontWeight: 600 }}> · {sub}</span> : ''}</div>
    </div>
  );

  const dInput = { border: '1px solid #e2e5ea', borderRadius: 8, padding: '7px 10px', fontSize: 13, fontFamily: 'inherit', color: '#3b414b', background: '#fff', outline: 'none' };
  const th = { textAlign: 'left', fontSize: 11.5, color: '#9aa1ab', fontWeight: 700, padding: '0 10px 8px', whiteSpace: 'nowrap' };
  const td = { fontSize: 13, color: '#2b2f38', padding: '9px 10px', borderTop: '1px solid #f4f5f7', whiteSpace: 'nowrap' };

  return (
    <Page title={t('nav.analytics')}>
      {/* 期間ツールバー */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 18, flexWrap: 'wrap' }}>
        <div style={{ display: 'inline-flex', background: '#eef0f3', borderRadius: 9, padding: 3 }}>
          {modeBtn('month', t('an.mode.month'))}{modeBtn('week', t('an.mode.week'))}{modeBtn('day', t('an.mode.day'))}
        </div>
        <button onClick={() => setShowRange(true)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '7px 12px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600,
          border: '1px solid ' + (mode === 'range' ? '#4a5af0' : '#e2e5ea'), background: mode === 'range' ? '#eef0fe' : '#fff', color: mode === 'range' ? '#4a5af0' : '#3b414b' }}>
          <Icon name="filter" size={14} stroke={2} />{t('an.rangePicker')}
        </button>
        {mode !== 'range' && (
          <div style={{ display: 'flex', gap: 4 }}>
            <IconButton name="chevronLeft" size={18} onClick={() => shift(-1)} />
            <IconButton name="chevronRight" size={18} onClick={() => shift(1)} />
          </div>
        )}
        <div style={{ fontSize: 16, fontWeight: 700, color: '#1c1f26' }}>{periodLabel}</div>
        {mode !== 'range' && <Button variant="default" size="sm" onClick={() => setAnchor(today())}>{t('an.toToday')}</Button>}
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 12 }}>
          <Button variant="default" size="sm" icon="download" onClick={exportReport} title={window.t("extra.analytics.printHint")}>{window.t("extra.analytics.print")}</Button>
          <div style={{ fontSize: 12.5, color: '#9aa1ab' }}>{t('an.targetCount', { n: total })}</div>
        </div>
      </div>

      {/* KPIカードは「総合評価（全体）」カードの先頭に統合（2026-07-16 レイアウト結合） */}

      {/* 総合評価（全体）を最上部に（2026-07-16 ユーザー指定）。成約率・月別テーブルはこのカードの下へ移動 */}
      {/* 総合評価（全体）— この期間に開いた案件全体の状況（案件状況・発注者の反応・商談の進展）＋主要KPI＋AI総合コメント */}
      <Card title={t('an.overall.title')} action={<Button variant="primary" size="sm" icon="spark" onClick={genAi} disabled={aiBusy}>{aiBusy ? t('an.aiReview.generating') : (ai ? t('an.aiReview.regenerate') : t('an.aiReview.generate'))}</Button>}>
        {/* 主要KPI（案件数・成約・パイプライン/成約金額＋前期比）＝全体サマリーの先頭に統合 */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(158px, 1fr))', gap: 12, marginBottom: 16 }}>
          {[
            { lb: '案件数', v: total, prev: prevTotal, c: '#4a5af0', bg: '#eef0fe' },
            { lb: '成約', v: wonPeriod, prev: prevWon, c: '#15803d', bg: '#e3f5e9' },
            { lb: 'パイプライン金額', v: pipelineAmount, prev: prevPipeline, c: '#b45309', bg: '#fdf0db', money: true },
            { lb: '成約金額', v: wonAmount, prev: prevWonAmount, c: '#0f766e', bg: '#dcf3f0', money: true },
          ].map(k => { const d = delta(k.v, k.prev); const emptyMoney = k.money && !k.v; return (
            <div key={k.lb} style={{ padding: '14px 16px', background: k.bg, borderRadius: 12 }}>
              <div style={{ fontSize: 12, color: '#5b626d', fontWeight: 600 }}>{k.lb}</div>
              <div style={{ fontSize: emptyMoney ? 16 : 23, fontWeight: 800, color: emptyMoney ? '#b98d54' : k.c, marginTop: emptyMoney ? 5 : 2, whiteSpace: 'nowrap' }}>{emptyMoney ? window.t("extra.analytics.noQuote") : (k.money ? yen(k.v) : <>{k.v}<span style={{ fontSize: 13, fontWeight: 700 }}> {t('unit.count')}</span></>)}</div>
              {emptyMoney
                ? <div style={{ fontSize: 11, marginTop: 3, color: '#aab0ba', fontWeight: 500 }}>{window.t("extra.analytics.noQuoteHint")}</div>
                : <div style={{ fontSize: 11.5, marginTop: 3, fontWeight: 700, color: d == null ? '#9aa1ab' : d >= 0 ? '#15803d' : '#dc2626' }}>{d == null ? '—' : (d >= 0 ? '▲ +' + d : '▼ ' + d) + '%'}<span style={{ color: '#9aa1ab', fontWeight: 500 }}>{window.t("extra.analytics.previous")}</span></div>}
            </div>
          ); })}
        </div>
        {(() => {
          const tile = { background: '#fafbfc', border: '1px solid #eef0f3', borderRadius: 12, padding: '14px 16px', minWidth: 0 };
          const tileLabel = { fontSize: 11.5, fontWeight: 700, color: '#9aa1ab', marginBottom: 9, letterSpacing: '.02em' };
          return (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 12, marginBottom: 16 }}>
              {/* 案件の状況 */}
              <div style={tile}>
                <div style={tileLabel}>{t('an.overall.caseStatus')}</div>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 9 }}>
                  <b style={{ fontSize: 22, color: '#1f2430' }}>{total}</b>
                  <span style={{ fontSize: 12, color: '#9aa1ab' }}>{t('an.overall.target')}・{t('unit.count')}</span>
                </div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                  {statusList.filter(s => statusDist[s] > 0).map(s => { const st = D.STATUS[s] || {}; return (
                    <span key={s} style={{ fontSize: 11.5, fontWeight: 700, color: st.fg || '#4b5563', background: st.soft || '#eef0f2', padding: '2px 8px', borderRadius: 999, whiteSpace: 'nowrap' }}>{st.label || s} {statusDist[s]}</span>
                  ); })}
                  {!total && <span style={{ fontSize: 12.5, color: '#c4c9d0' }}>—</span>}
                </div>
              </div>
              {/* 発注者満足度（客の反応） */}
              <div style={tile}>
                <div style={tileLabel}>{t('an.col.satisfaction')}</div>
                {satTotalN ? (
                  <>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
                      <b style={{ fontSize: 24, color: satColor(satAvg) }}>{satAvg.toFixed(1)}</b><span style={{ fontSize: 13, color: '#9aa1ab' }}>/4</span>
                      <span style={{ fontSize: 12, color: '#9aa1ab', marginLeft: 6 }}>{t('an.overall.responseCount', { n: satTotalN })}</span>
                    </div>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 9 }}>
                      {[4, 3, 2, 1].filter(v => satDist[v] > 0).map(v => { const m = satMeta(v); return (
                        <span key={v} style={{ fontSize: 11, fontWeight: 700, color: m.color, background: m.bg, padding: '2px 7px', borderRadius: 999, whiteSpace: 'nowrap' }}>{m.label} {satDist[v]}</span>
                      ); })}
                    </div>
                  </>
                ) : <div style={{ fontSize: 13, color: '#9aa1ab', paddingTop: 8 }}>{t('an.overall.noResponse')}</div>}
              </div>
              {/* 商談の進展（2回目/3回目への到達） */}
              <div style={tile}>
                <div style={tileLabel}>{t('an.overall.meetingProgress')}</div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: 12.5, color: '#3b414b', marginBottom: 9, flexWrap: 'wrap' }}>
                  <span><b style={{ fontSize: 15 }}>{mtgDist[1] || 0}</b> {t('an.meeting.1st')}</span>
                  <Icon name="chevronRight" size={12} stroke={2} style={{ color: '#c4c9d0' }} />
                  <span><b style={{ fontSize: 15 }}>{reached2}</b> {t('an.meeting.2nd')}+</span>
                  <Icon name="chevronRight" size={12} stroke={2} style={{ color: '#c4c9d0' }} />
                  <span><b style={{ fontSize: 15 }}>{reached3}</b> {t('an.meeting.3rd')}+</span>
                </div>
                <div style={{ fontSize: 12, color: '#9aa1ab' }}>{t('an.overall.advanceRate')}: <b style={{ color: advanceRate >= 50 ? '#16a34a' : advanceRate >= 30 ? '#d97706' : '#6b7280' }}>{advanceRate}%</b></div>
              </div>
            </div>
          );
        })()}
        {ai
          ? <div style={{ fontSize: 13.5, color: '#2b2f38', lineHeight: 1.85, whiteSpace: 'pre-wrap' }}>{ai}</div>
          : <div style={{ padding: '14px 16px', textAlign: 'center', color: '#9aa1ab', fontSize: 12.5, lineHeight: 1.7, background: '#fafbfc', borderRadius: 10, border: '1px dashed #e2e5ea' }}>
              {t('an.overall.emptyDesc')}
            </div>}
        {/* 生成した総評は自動でDBへ保存。過去分はここから調閲・削除できる */}
        <AiReportHistory type="analytics_summary" title={t('an.aiReview.historyTitle')} />
      </Card>

      {/* 段階別の成約率（総合評価の下へ移動） */}
      <Card title={window.t("extra.analytics.currentWinRate")} style={{ marginBottom: 16 }}>
        <div style={{ display: 'flex', gap: 20, flexWrap: 'wrap' }}>
          {[[window.t("label.extra2"), convCase, won + '/' + total], [window.t("label.extra3"), convMtg, won + '/' + withMtg], [window.t("label.extra4"), convProposal, won + '/' + submitted]].map(([lb, rate, sub]) => (
            <div key={lb} style={{ flex: 1, minWidth: 150 }}>
              <div style={{ fontSize: 12, color: '#5b626d', fontWeight: 600, marginBottom: 4 }}>{lb}</div>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}><span style={{ fontSize: 24, fontWeight: 800, color: '#4a5af0' }}>{rate}%</span><span style={{ fontSize: 11.5, color: '#9aa1ab' }}>{sub}</span></div>
              <div style={{ height: 8, background: '#f0f1f4', borderRadius: 999, overflow: 'hidden', marginTop: 5 }}><div style={{ width: rate + '%', height: '100%', background: '#4a5af0', borderRadius: 999 }} /></div>
            </div>
          ))}
        </div>
        <div style={{ fontSize: 11.5, color: '#9aa1ab', marginTop: 12, lineHeight: 1.6 }}>{window.t("extra.analytics.fromStage")}<b>{window.t("extra.analytics.reachWon")}</b>{window.t("extra.analytics.funnelHint1")}<b>{window.t("extra.analytics.reachProposal")}</b>{window.t("extra.analytics.funnelHint2")}</div>
      </Card>

      {/* 月別 商談回数・成約/失注（ダッシュボードと同じ集計・ドリルダウン付き＝全社ベースで直近12ヶ月）。
         成約列は成約(受注)＝status 'won' を成約日で集計。各セルをクリックすると会社・案件の内訳が開く。 */}
      <Card title={t('an.monthlyStats')} style={{ marginBottom: 16 }}
        action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{t('an.monthlyStats.desc')}</span>}>
        <MonthlyStatsTable cases={cases} months={12} wonLabel={t('an.col.won')} />
      </Card>

      {/* 商談回数の担当者別内訳：1回目/2回目/3回目以上を「誰が」何件行ったか（月別テーブルと同じ集計規則・月で絞り込み可） */}
      <Card title={window.t("extra.analytics.meetingsByOwner")} style={{ marginBottom: 16 }}
        action={<span style={{ fontSize: 12, color: '#9aa1ab' }}>{window.t("extra.analytics.companyDrill")}</span>}>
        <StageOwnerTable cases={cases} months={12} />
      </Card>

      {/* 失注分析（AI）：失注案件を横断して負けた理由の共通テーマ＋改善策を抽出 */}
      <LossAnalysisCard />

      {/* 経由別ファネル：今期(期間連動)／累計 切替。サマリー扱いで上に配置 */}
      <Card title={window.t("extra.analytics.channelFunnel") + (funnelAll ? window.t("extra.analytics.cumulative") : window.t("extra.analytics.current")) + window.t("extra.analytics.funnelSteps")}
        action={
          <div style={{ display: 'inline-flex', background: '#eef0f3', borderRadius: 8, padding: 3 }}>
            {[['period', window.t("extra.analytics.current")], ['all', window.t("extra.analytics.cumulative")]].map(([k, lb]) => { const active = (funnelAll ? 'all' : 'period') === k; return (
              <button key={k} onClick={() => setFunnelAll(k === 'all')} style={{ padding: '5px 14px', borderRadius: 6, border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 700,
                background: active ? '#fff' : 'transparent', color: active ? '#1f2430' : '#7b828d', boxShadow: active ? '0 1px 2px rgba(20,22,40,.12)' : 'none' }}>{lb}</button>
            ); })}
          </div>
        }
        style={{ marginTop: 16 }}>
        {viaFunnel.length === 0 ? (
          <div style={{ padding: '20px 4px', color: '#9aa1ab', fontSize: 13 }}>{funnelAll ? window.t("extra.analytics.noSourceData") : window.t("extra.analytics.noPeriodData")}</div>
        ) : (
          <div style={{ overflowX: 'auto' }}>
           <div style={{ minWidth: 480 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '108px repeat(4, 1fr)', gap: 8, padding: '0 4px 8px', fontSize: 11, fontWeight: 700, color: '#9aa1ab' }}>
              <div>{window.t("extra.common.source")}</div><div style={{ textAlign: 'right' }}>{window.t("extra.common.lead")}</div><div style={{ textAlign: 'right' }}>{window.t("apo.col.status")}</div><div style={{ textAlign: 'right' }}>{window.t("extra.common.converted")}</div><div style={{ textAlign: 'right' }}>{window.t("an.col.won")}</div>
            </div>
            {viaFunnel.map(g => {
              const entRate = g.leads ? Math.round(g.entered / g.leads * 100) : null;
              const wonRate = g.deals ? Math.round(g.won / g.deals * 100) : null;
              const cell = (n, sub, subColor) => <div style={{ textAlign: 'right', lineHeight: 1.25 }}><div style={{ fontSize: 15, fontWeight: 700, color: '#1f2430' }}>{n}</div>{sub != null && <div style={{ fontSize: 10.5, color: subColor, fontWeight: 700 }}>{sub}</div>}</div>;
              return (
                <div key={g.meta.key} style={{ display: 'grid', gridTemplateColumns: '108px repeat(4, 1fr)', gap: 8, alignItems: 'center', padding: '11px 4px', borderTop: '1px solid #f4f5f7' }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11.5, fontWeight: 700, color: g.meta.color, background: g.meta.bg, padding: '3px 8px', borderRadius: 999, whiteSpace: 'nowrap', justifySelf: 'start' }}><Icon name={g.meta.icon} size={11} stroke={2.2} />{g.meta.label}</span>
                  {cell(g.leads)}
                  {cell(g.entered, entRate != null ? entRate + '%' : null, '#4a5af0')}
                  {cell(g.deals)}
                  {cell(g.won, wonRate != null ? wonRate + '%' : null, '#15803d')}
                </div>
              );
            })}
            <div style={{ fontSize: 11.5, color: '#9aa1ab', marginTop: 12, lineHeight: 1.6 }}>{window.t("extra.analytics.leadDefinition")}<b style={{ color: '#4a5af0' }}>{window.t("extra.analytics.entryRate")}</b>{window.t("extra.analytics.entryDefinition")}<b style={{ color: '#15803d' }}>{window.t("an.col.winRate")}</b>{window.t("extra.analytics.winDefinition")}<b>{window.t("extra.analytics.compareChannel")}</b>{window.t("extra.analytics.compareHint")}<br />{funnelAll ? window.t("extra.analytics.totalDefinition") : window.t("extra.analytics.periodDefinition")}</div>
           </div>
          </div>
        )}
      </Card>

      {/* 詳細分析（担当者・グラフ）— 普段は畳んでサマリーを見やすく */}
      <button onClick={() => setShowDetail(v => !v)} style={{ width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 16, padding: '13px 18px', borderRadius: 12, cursor: 'pointer', fontFamily: 'inherit', border: '1px solid #e6e8ec', background: '#fff' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 9, fontSize: 14.5, fontWeight: 700, color: '#1f2430' }}><Icon name="chart" size={16} stroke={2} style={{ color: '#4a5af0' }} />{window.t("extra.analytics.details")}</span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12.5, color: '#7b828d', fontWeight: 600 }}>{showDetail ? window.t("btn.close") : window.t("btn.open")}<Icon name="chevronDown" size={16} stroke={2.2} style={{ transform: showDetail ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} /></span>
      </button>

      {showDetail && (
        <>
          {/* タブ：担当者別／全体分布 */}
          <div style={{ display: 'inline-flex', background: '#eef0f3', borderRadius: 9, padding: 3, marginTop: 14 }}>
            {[['owner', window.t("label.extra6")], ['dist', window.t("label.extra7")]].map(([k, lb]) => (
              <button key={k} onClick={() => setDetailTab(k)} style={{ padding: '7px 18px', borderRadius: 7, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 700, border: 'none', background: detailTab === k ? '#fff' : 'transparent', color: detailTab === k ? '#1f2430' : '#7b828d', boxShadow: detailTab === k ? '0 1px 2px rgba(16,24,40,.12)' : 'none' }}>{lb}</button>
            ))}
          </div>

          {detailTab === 'owner' && (<>
            {/* 担当者パフォーマンス */}
            <Card title={t('an.ownerPerf.title')} style={{ marginTop: 14 }}>
              {owners.length === 0
                ? <div style={{ padding: '24px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.ownerPerf.empty')}</div>
                : (
                  <div style={{ overflowX: 'auto' }}>
                    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                      <thead><tr>
                        <th style={th}>{t('an.col.owner')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.cases')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.share')}</th>
                        <th style={{ ...th, textAlign: 'right' }}>{t('an.col.temperature')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.satisfaction')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.won')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.winRate')}</th>
                        <th style={{ ...th, textAlign: 'right' }}>{t('an.col.proposalSubmitted')}</th><th style={{ ...th, textAlign: 'right' }}>{t('an.col.avgMeetings')}</th><th style={{ ...th, textAlign: 'left' }}>{t('an.col.bestStage')}</th>
                      </tr></thead>
                      <tbody>
                        {owners.map(o => (
                          <tr key={o.id}>
                            <td style={td}><span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}><span style={{ width: 9, height: 9, borderRadius: 3, background: o.color }} />{o.name}</span></td>
                            <td style={{ ...td, textAlign: 'right', fontWeight: 700 }}>{drillNum(o.n, o.nCases, o.name + '｜' + t('an.col.cases'))}</td>
                            <td style={{ ...td, textAlign: 'right', color: '#7b828d' }}>{total ? Math.round(o.n / total * 100) : 0}%</td>
                            <td style={{ ...td, textAlign: 'right' }}>{o.temp != null ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><span style={{ width: 8, height: 8, borderRadius: '50%', background: tempColor(o.temp) }} /><b style={{ color: tempColor(o.temp) }}>{o.temp}°</b></span> : '—'}</td>
                            <td style={{ ...td, textAlign: 'right' }}>{o.sat != null ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><b style={{ color: satColor(o.sat) }}>{o.sat.toFixed(1)}</b><span style={{ fontSize: 11, color: '#9aa1ab' }}>/4・n{o.satN}</span></span> : <span style={{ color: '#c4c9d0' }}>—</span>}</td>
                            <td style={{ ...td, textAlign: 'right', fontWeight: 700, color: o.won ? '#15803d' : '#c4c9d0' }}>{drillNum(o.won, o.wonCases, o.name + '｜' + t('an.col.won'), { color: o.won ? '#15803d' : '#c4c9d0', fontWeight: 700 })}</td>
                            <td style={{ ...td, textAlign: 'right', fontWeight: 700, color: o.won ? '#15803d' : '#9aa1ab' }}>{drillNum((o.n ? Math.round(o.won / o.n * 100) : 0) + '%', o.wonCases, o.name + '｜' + t('an.col.won'), { color: o.won ? '#15803d' : '#9aa1ab', fontWeight: 700 })}</td>
                            <td style={{ ...td, textAlign: 'right' }}>{drillNum(o.submitted, o.submittedCases, o.name + '｜' + t('an.col.proposalSubmitted'))}</td>
                            <td style={{ ...td, textAlign: 'right' }}>{t('an.timesValue', { n: o.avg.toFixed(1) })}</td>
                            <td style={{ ...td, color: '#4a5af0', fontWeight: 600 }}>{o.best}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                )}
              <div style={{ fontSize: 11, color: '#aab0ba', marginTop: 10, lineHeight: 1.6 }}>{t('an.ownerPerf.footnote')}</div>
            </Card>

            {/* 担当者別：案件数構成＋商談温度感 */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 16, marginTop: 16 }}>
              <Card title={t('an.chart.ownerVolume')}>
                {owners.length ? <ChartCanvas type="doughnut" data={ownerDonut} options={donutOpts} height={230} /> : <div style={{ padding: '30px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.noData')}</div>}
              </Card>
              <Card title={t('an.tempStack.title')}>
                {owners.length ? <ChartCanvas type="bar" data={tempStack} options={stackOpts} height={230} /> : <div style={{ padding: '30px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.noData')}</div>}
                <div style={{ fontSize: 11, color: '#aab0ba', marginTop: 10, lineHeight: 1.6 }}>{t('an.tempStack.footnote')}</div>
              </Card>
            </div>

            {/* 担当者別の発注者満足度 */}
            <Card title={t('an.satOwner.title')} style={{ marginTop: 16 }}>
              {satOwners.length
                ? <>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 8 }}>
                      <span style={{ fontSize: 13, color: '#7b828d' }}>{t('an.overallAvg')}</span>
                      <b style={{ fontSize: 20, color: satColor(satAvg) }}>{satAvg.toFixed(1)}<span style={{ fontSize: 13, color: '#9aa1ab', fontWeight: 600 }}> /4</span></b>
                      <span style={{ fontSize: 12, color: '#9aa1ab' }}>{t('an.responseCount', { n: satTotalN })}</span>
                    </div>
                    <ChartCanvas type="bar" data={satBar} options={satBarOpts} height={Math.max(200, satOwners.length * 44 + 80)} />
                  </>
                : <div style={{ padding: '30px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.satOwner.empty1')}<br />{t('an.satOwner.empty2')}</div>}
              <div style={{ fontSize: 11, color: '#aab0ba', marginTop: 10, lineHeight: 1.6 }}>{t('an.satOwner.footnote')}</div>
            </Card>
          </>)}

          {detailTab === 'dist' && (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 16, marginTop: 14 }}>
              <Card title={t('an.chart.meetingStages')}>
                <ChartCanvas type="bar" data={mtgBar} options={barOpts} height={230} />
              </Card>
              <Card title={t('an.chart.rankDist')}>
                {total ? <ChartCanvas type="doughnut" data={rankDonut} options={donutOpts} height={230} /> : <div style={{ padding: '30px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.noData')}</div>}
              </Card>
              <Card title={t('an.chart.satDist')}>
                {satTotalN ? <ChartCanvas type="doughnut" data={satDonut} options={donutOpts} height={230} /> : <div style={{ padding: '30px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('an.noSatResponses')}</div>}
              </Card>
            </div>
          )}
        </>
      )}

      {/* 期間指定モーダル */}
      {showRange && (
        <Modal open onClose={() => setShowRange(false)} width={420} title={t('an.rangeModal.title')}
          footer={<>
            <Button variant="subtle" onClick={() => setShowRange(false)}>{t('btn.cancel')}</Button>
            <Button variant="primary" icon="check" onClick={() => { setMode('range'); setShowRange(false); }}>{t('an.rangeModal.analyze')}</Button>
          </>}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0' }}>
            <span style={{ fontSize: 13, color: '#3b414b', fontWeight: 600, width: 48 }}>{t('an.rangeModal.start')}</span>
            <input type="date" value={rs} onChange={(e) => setRs(e.target.value)} style={{ ...dInput, flex: 1 }} />
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0' }}>
            <span style={{ fontSize: 13, color: '#3b414b', fontWeight: 600, width: 48 }}>{t('an.rangeModal.end')}</span>
            <input type="date" value={re} onChange={(e) => setRe(e.target.value)} style={{ ...dInput, flex: 1 }} />
          </div>
          <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 8, lineHeight: 1.6 }}>{t('an.rangeModal.help')}</div>
        </Modal>
      )}

      {/* 担当者パフォーマンス等の 数字クリック → 内訳（会社・案件）モーダル（共有部品） */}
      {drill && <CaseListModal title={drill.title} cases={drill.cases} onClose={() => setDrill(null)} />}
    </Page>
  );
}

Object.assign(window, { AnalyticsScreen });
