/* =====================================================================
   Iwaki Pharma Board (IPB) - Pharmacy Insight Dashboard
   v3: + AI Insight Summary + Multi-select Comparison Mode
   React + Recharts (UMD via CDN) / In-browser Babel JSX
   ===================================================================== */

const { useState, useEffect, useMemo, useRef, useCallback } = React;
const {
  ResponsiveContainer, ComposedChart, LineChart,
  Bar, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, LabelList,
} = Recharts;

/* ---------- 定数 ---------- */
const COLORS = {
  blue:   '#1E5AAA',
  orange: '#F4A340',
  green:  '#3DA968',
  red:    '#E0524A',
  text:   '#2C3E50',
  border: '#E5E7EB',
  gray:   '#94A3B8',
  purple: '#7C3AED',
  teal:   '#0EA5E9',
};

/* 4項目の売上構成カラー */
const FEE_COLORS = {
  drugFee:         '#F4A340', // 薬剤料
  pharmMgmtFee:    '#7C3AED', // 薬学管理料
  dispenseTechFee: '#1E5AAA', // 調剤技術料
  medMaterialFee:  '#0EA5E9', // 医療材料料
};

const FEE_LABELS = {
  drugFee:         '薬剤料',
  pharmMgmtFee:    '薬学管理料',
  dispenseTechFee: '調剤技術料',
  medMaterialFee:  '医療材料料',
};

const FEE_KEYS = ['drugFee', 'pharmMgmtFee', 'dispenseTechFee', 'medMaterialFee'];

/* 比較モード用 シリーズカラーパレット（最大8系列） */
const SERIES_COLORS = [
  '#1E5AAA', '#F4A340', '#3DA968', '#E0524A',
  '#7C3AED', '#0EA5E9', '#DB2777', '#65A30D',
];

const STORAGE_KEY = 'ipb_dashboard_v4';

/* ---------- 店舗マスタ ---------- */
const STORES = [
  { id: 'minamigaoka', name: 'フラワー薬局南ヶ丘病院店', short: '南ヶ丘病院店', company: 'iwaki',    type: 'store' },
  { id: 'magae',       name: 'フラワー薬局馬替店',         short: '馬替店',       company: 'iwaki',    type: 'store' },
  { id: 'nonoichi',    name: 'ののいちフラワー薬局',       short: 'ののいち',     company: 'iwaki',    type: 'store' },
  { id: 'kenroku',     name: '兼六薬局',                   short: '兼六',         company: 'sincere',  type: 'store' },
  { id: 'okyozuka',    name: '兼六薬局御経塚店',           short: '御経塚店',     company: 'sincere',  type: 'store' },
];

const COMPANIES = [
  { id: 'iwaki',   name: '有限会社いわ木 合計',     short: 'いわ木',     storeIds: ['minamigaoka', 'magae', 'nonoichi'] },
  { id: 'sincere', name: '有限会社シンシアー 合計', short: 'シンシアー', storeIds: ['kenroku', 'okyozuka'] },
];

const ALL_TAB_ID = 'all';

const TABS = [
  ...STORES.map(s => ({ id: s.id, label: s.short, fullName: s.name, kind: 'store',   icon: 'fa-prescription-bottle-medical' })),
  ...COMPANIES.map(c => ({ id: c.id, label: c.short, fullName: c.name, kind: 'company', icon: 'fa-building', storeIds: c.storeIds })),
  { id: ALL_TAB_ID, label: '5店舗合計', fullName: '5店舗 全社合計', kind: 'all', icon: 'fa-chart-pie' },
];

/* ---------- ダミーデータ ----------
   売上構成 4項目（万円）:
     drugFee         : 薬剤料        （比率 約57%）
     pharmMgmtFee    : 薬学管理料    （比率 約10%）
     dispenseTechFee : 調剤技術料    （比率 約27%）
     medMaterialFee  : 医療材料料    （比率 約 6%）
   inventoryAmount: 月末在庫金額（万円） — 薬剤料の約1.5〜2ヶ月分を想定
*/
const sampleBase = [
  { month: "2025-05", workingDays: 24, drugFee: 1600, pharmMgmtFee: 280, dispenseTechFee: 750, medMaterialFee: 170, inventoryAmount: 2800 },
  { month: "2025-06", workingDays: 25, drugFee: 1660, pharmMgmtFee: 295, dispenseTechFee: 780, medMaterialFee: 178, inventoryAmount: 2900 },
  { month: "2025-07", workingDays: 26, drugFee: 1720, pharmMgmtFee: 305, dispenseTechFee: 810, medMaterialFee: 185, inventoryAmount: 3000 },
  { month: "2025-08", workingDays: 24, drugFee: 1690, pharmMgmtFee: 298, dispenseTechFee: 795, medMaterialFee: 180, inventoryAmount: 2960 },
  { month: "2025-09", workingDays: 25, drugFee: 1760, pharmMgmtFee: 312, dispenseTechFee: 830, medMaterialFee: 190, inventoryAmount: 3080 },
  { month: "2025-10", workingDays: 26, drugFee: 1820, pharmMgmtFee: 322, dispenseTechFee: 855, medMaterialFee: 198, inventoryAmount: 3180 },
  { month: "2025-11", workingDays: 24, drugFee: 1860, pharmMgmtFee: 330, dispenseTechFee: 875, medMaterialFee: 202, inventoryAmount: 3260 },
  { month: "2025-12", workingDays: 25, drugFee: 1920, pharmMgmtFee: 340, dispenseTechFee: 900, medMaterialFee: 210, inventoryAmount: 3360 },
  { month: "2026-01", workingDays: 22, drugFee: 1880, pharmMgmtFee: 332, dispenseTechFee: 880, medMaterialFee: 205, inventoryAmount: 3300 },
  { month: "2026-02", workingDays: 23, drugFee: 1950, pharmMgmtFee: 345, dispenseTechFee: 915, medMaterialFee: 215, inventoryAmount: 3420 },
  { month: "2026-03", workingDays: 25, drugFee: 2020, pharmMgmtFee: 358, dispenseTechFee: 945, medMaterialFee: 222, inventoryAmount: 3540 },
  { month: "2026-04", workingDays: 24, drugFee: 2080, pharmMgmtFee: 368, dispenseTechFee: 975, medMaterialFee: 230, inventoryAmount: 3640 },
];
const SAMPLE_FACTORS = {
  minamigaoka: 1.20, magae: 0.95, nonoichi: 1.05, kenroku: 1.10, okyozuka: 0.85,
};
/* 店舗ごとの在庫管理レベル感（小さいほど在庫が締まっている） */
const SAMPLE_INV_FACTORS = {
  minamigaoka: 1.10, magae: 0.95, nonoichi: 1.00, kenroku: 1.20, okyozuka: 0.90,
};
function makeSample(storeId) {
  const f = SAMPLE_FACTORS[storeId] ?? 1;
  const invF = (SAMPLE_INV_FACTORS[storeId] ?? 1) * f;
  return sampleBase.map(d => ({
    month: d.month, workingDays: d.workingDays,
    drugFee:         Math.round(d.drugFee         * f),
    pharmMgmtFee:    Math.round(d.pharmMgmtFee    * f),
    dispenseTechFee: Math.round(d.dispenseTechFee * f),
    medMaterialFee:  Math.round(d.medMaterialFee  * f),
    inventoryAmount: Math.round((d.inventoryAmount || 0) * invF),
  }));
}
function makeAllSamples() {
  const obj = {}; STORES.forEach(s => { obj[s.id] = makeSample(s.id); }); return obj;
}

/* ---------- ユーティリティ ---------- */
const fmt = (n, digits = 0) => {
  if (n === null || n === undefined || isNaN(n)) return '-';
  return Number(n).toLocaleString('ja-JP', { minimumFractionDigits: digits, maximumFractionDigits: digits });
};
const fmt1 = (n) => fmt(n, 1);
const todayStr = () => {
  const d = new Date();
  return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
};

/* ---------- 計算 ---------- */
/* 旧形式（techFee+drugFee）データを 4項目構成に自動マイグレーション */
function migrateRow(d) {
  if (d.dispenseTechFee !== undefined || d.pharmMgmtFee !== undefined || d.medMaterialFee !== undefined) {
    return d; // 既に新形式
  }
  if (d.techFee !== undefined) {
    // 旧 techFee → dispenseTechFee に振替（薬学管理料・医療材料料 は 0 で埋める）
    return {
      month: d.month,
      workingDays: d.workingDays,
      drugFee: Number(d.drugFee) || 0,
      pharmMgmtFee: 0,
      dispenseTechFee: Number(d.techFee) || 0,
      medMaterialFee: 0,
      inventoryAmount: Number(d.inventoryAmount) || 0,
    };
  }
  return d;
}
function calcRows(data) {
  return (data || []).map(migrateRow).map(d => {
    const drugFee         = Number(d.drugFee)         || 0;
    const pharmMgmtFee    = Number(d.pharmMgmtFee)    || 0;
    const dispenseTechFee = Number(d.dispenseTechFee) || 0;
    const medMaterialFee  = Number(d.medMaterialFee)  || 0;
    const inv             = Number(d.inventoryAmount) || 0;
    const total = drugFee + pharmMgmtFee + dispenseTechFee + medMaterialFee;
    const wd    = d.workingDays > 0 ? d.workingDays : 0;
    return {
      ...d,
      drugFee, pharmMgmtFee, dispenseTechFee, medMaterialFee,
      inventoryAmount: inv,
      total,
      drugPerDay:         wd > 0 ? drugFee         / wd : 0,
      pharmMgmtPerDay:    wd > 0 ? pharmMgmtFee    / wd : 0,
      dispenseTechPerDay: wd > 0 ? dispenseTechFee / wd : 0,
      medMaterialPerDay:  wd > 0 ? medMaterialFee  / wd : 0,
      totalPerDay:        wd > 0 ? total           / wd : 0,
    };
  });
}
function calcGrowth(rows) {
  if (rows.length < 2) {
    return {
      drugGrowth: 1, pharmMgmtGrowth: 1, dispenseTechGrowth: 1, medMaterialGrowth: 1,
      totalGrowth: 1, perDayGrowth: 1,
      inventoryGrowth: 1,
      valid: false,
    };
  }
  const first = rows[0], last = rows[rows.length - 1];
  const safe = (a, b) => (b > 0 ? a / b : 0);
  const fInv = first.inventoryAmount || 0;
  const lInv = last.inventoryAmount  || 0;
  return {
    drugGrowth:         safe(last.drugFee,         first.drugFee),
    pharmMgmtGrowth:    safe(last.pharmMgmtFee,    first.pharmMgmtFee),
    dispenseTechGrowth: safe(last.dispenseTechFee, first.dispenseTechFee),
    medMaterialGrowth:  safe(last.medMaterialFee,  first.medMaterialFee),
    totalGrowth:        safe(last.total,           first.total),
    perDayGrowth:       safe(last.totalPerDay,     first.totalPerDay),
    inventoryGrowth:    fInv > 0 ? lInv / fInv : 0,
    first, last, valid: true,
  };
}
function aggregateStores(stores, storeIds) {
  const monthMap = new Map();
  storeIds.forEach(id => {
    (stores[id] || []).map(migrateRow).forEach(d => {
      const cur = monthMap.get(d.month) || {
        month: d.month, drugFee: 0, pharmMgmtFee: 0, dispenseTechFee: 0, medMaterialFee: 0,
        inventoryAmount: 0, workingDays: 0, count: 0,
      };
      cur.drugFee         += Number(d.drugFee)         || 0;
      cur.pharmMgmtFee    += Number(d.pharmMgmtFee)    || 0;
      cur.dispenseTechFee += Number(d.dispenseTechFee) || 0;
      cur.medMaterialFee  += Number(d.medMaterialFee)  || 0;
      cur.inventoryAmount += Number(d.inventoryAmount) || 0;
      cur.workingDays      = Math.max(cur.workingDays, Number(d.workingDays) || 0);
      cur.count           += 1;
      monthMap.set(d.month, cur);
    });
  });
  return Array.from(monthMap.values()).sort((a, b) => String(a.month).localeCompare(String(b.month)));
}

/* タブIDから rows を返す（共通アクセサ） */
function getRowsForTab(tabId, stores) {
  const tab = TABS.find(t => t.id === tabId);
  if (!tab) return [];
  let raw = [];
  if (tab.kind === 'store') {
    raw = [...(stores[tab.id] || [])].sort((a, b) => String(a.month).localeCompare(String(b.month)));
  } else if (tab.kind === 'company') {
    raw = aggregateStores(stores, tab.storeIds);
  } else {
    raw = aggregateStores(stores, STORES.map(s => s.id));
  }
  return calcRows(raw);
}

/* ---------- CSV ----------
   新ヘッダ: month,workingDays,drugFee,pharmMgmtFee,dispenseTechFee,medMaterialFee[,inventoryAmount]
   旧ヘッダ（後方互換）: month,workingDays,techFee,drugFee[,inventoryAmount]
     → techFee は dispenseTechFee に振替、pharmMgmtFee / medMaterialFee は 0
*/
function parseCSV(text) {
  const lines = text.replace(/\r/g, '').split('\n').filter(l => l.trim() !== '');
  if (lines.length < 2) throw new Error('CSVにデータがありません');
  const header = lines[0].split(',').map(s => s.trim());
  const idx = {
    month:           header.indexOf('month'),
    workingDays:     header.indexOf('workingDays'),
    drugFee:         header.indexOf('drugFee'),
    pharmMgmtFee:    header.indexOf('pharmMgmtFee'),
    dispenseTechFee: header.indexOf('dispenseTechFee'),
    medMaterialFee:  header.indexOf('medMaterialFee'),
    techFee:         header.indexOf('techFee'),
    inventoryAmount: header.indexOf('inventoryAmount'),
  };
  if (idx.month < 0 || idx.workingDays < 0 || idx.drugFee < 0) {
    throw new Error('ヘッダーは month,workingDays,drugFee,pharmMgmtFee,dispenseTechFee,medMaterialFee[,inventoryAmount] である必要があります');
  }
  // 新形式 / 旧形式どちらもOK：4項目どれかが揃っていない場合、旧 techFee で代用
  const isLegacy = idx.pharmMgmtFee < 0 && idx.dispenseTechFee < 0 && idx.medMaterialFee < 0 && idx.techFee >= 0;
  const rows = [];
  for (let i = 1; i < lines.length; i++) {
    const cols = lines[i].split(',').map(s => s.trim());
    const get = (k) => idx[k] >= 0 ? (Number(cols[idx[k]]) || 0) : 0;
    rows.push({
      month: cols[idx.month],
      workingDays: Number(cols[idx.workingDays]) || 0,
      drugFee:         get('drugFee'),
      pharmMgmtFee:    isLegacy ? 0 : get('pharmMgmtFee'),
      dispenseTechFee: isLegacy ? get('techFee') : get('dispenseTechFee'),
      medMaterialFee:  isLegacy ? 0 : get('medMaterialFee'),
      inventoryAmount: idx.inventoryAmount >= 0 ? (Number(cols[idx.inventoryAmount]) || 0) : 0,
    });
  }
  return rows;
}

/* =====================================================================
   AI 総評生成（データ駆動の自然な日本語サマリー）
   ===================================================================== */
function generateInsight(rows, growth, tab) {
  if (!rows || rows.length === 0) {
    return {
      headline: 'データが不足しています',
      paragraphs: ['データが入力されると、AIによる経営総評をここに表示します。'],
      tags: [],
    };
  }

  const first = rows[0], last = rows[rows.length - 1];
  const totalSum = rows.reduce((a, b) => a + b.total, 0);
  const totalDays = rows.reduce((a, b) => a + (b.workingDays || 0), 0);
  const avgPerDay = totalDays > 0 ? totalSum / totalDays : 0;
  const avgMonth = totalSum / rows.length;

  // 最高/最低月
  const maxRow = rows.reduce((a, b) => (b.total > a.total ? b : a), rows[0]);
  const minRow = rows.reduce((a, b) => (b.total < a.total ? b : a), rows[0]);

  // 1日あたり最高/最低
  const maxDayRow = rows.reduce((a, b) => (b.totalPerDay > a.totalPerDay ? b : a), rows[0]);
  const minDayRow = rows.reduce((a, b) => (b.totalPerDay < a.totalPerDay ? b : a), rows[0]);

  // 構成比（直近月）— 4項目それぞれのシェア
  const ratios = FEE_KEYS.map(k => ({
    key: k, label: FEE_LABELS[k],
    value: last[k] || 0,
    ratio: last.total > 0 ? ((last[k] || 0) / last.total) * 100 : 0,
    growthKey: k.replace('Fee', 'Growth'),
  }));
  const sortedByRatio = [...ratios].sort((a, b) => b.ratio - a.ratio);
  const topItem = sortedByRatio[0];
  const bottomItem = sortedByRatio[sortedByRatio.length - 1];

  // トレンド：直近3ヶ月 vs 期間前半の平均
  const recent = rows.slice(-3);
  const earlier = rows.slice(0, Math.max(1, Math.floor(rows.length / 2)));
  const recentAvg = recent.reduce((a, b) => a + b.totalPerDay, 0) / recent.length;
  const earlierAvg = earlier.reduce((a, b) => a + b.totalPerDay, 0) / earlier.length;
  const recentDelta = earlierAvg > 0 ? (recentAvg / earlierAvg - 1) * 100 : 0;

  // ボラティリティ（変動係数）
  const mean = rows.reduce((a, b) => a + b.total, 0) / rows.length;
  const variance = rows.reduce((a, b) => a + Math.pow(b.total - mean, 2), 0) / rows.length;
  const stdev = Math.sqrt(variance);
  const cv = mean > 0 ? (stdev / mean) * 100 : 0; // %

  /* ---- ヘッドライン ---- */
  const totalPct = (growth.totalGrowth - 1) * 100;
  let headline = '';
  let mood = 'neutral'; // 'good' | 'warn' | 'bad' | 'neutral'

  if (!growth.valid) {
    headline = '基準月との比較に十分なデータが揃っていません。';
    mood = 'neutral';
  } else if (totalPct >= 15) {
    headline = `総売上は初月比 +${fmt(totalPct, 1)}% と力強い成長基調です。`;
    mood = 'good';
  } else if (totalPct >= 5) {
    headline = `総売上は初月比 +${fmt(totalPct, 1)}% と着実な伸びを示しています。`;
    mood = 'good';
  } else if (totalPct >= -3) {
    headline = `総売上は初月比 ${totalPct >= 0 ? '+' : ''}${fmt(totalPct, 1)}% で、ほぼ横ばいで推移しています。`;
    mood = 'neutral';
  } else if (totalPct >= -10) {
    headline = `総売上は初月比 ${fmt(totalPct, 1)}% と弱含みで、要因分析が必要です。`;
    mood = 'warn';
  } else {
    headline = `総売上は初月比 ${fmt(totalPct, 1)}% と大幅減で、早急なテコ入れを検討すべき水準です。`;
    mood = 'bad';
  }

  /* ---- 段落生成 ---- */
  const paragraphs = [];

  // ① サマリー（規模感）
  paragraphs.push(
    `対象期間 ${first.month} 〜 ${last.month}（${rows.length}ヶ月）の合計売上は ${fmt(totalSum)} 万円、` +
    `期間平均は月 ${fmt(avgMonth)} 万円、1日あたり ${fmt1(avgPerDay)} 万円/日 でした。` +
    `直近月（${last.month}）は ${fmt(last.total)} 万円で、初月（${first.month}）の ${fmt(first.total)} 万円から ` +
    `×${fmt(growth.totalGrowth, 2)}（${totalPct >= 0 ? '+' : ''}${fmt(totalPct, 1)}%）の推移となっています。`
  );

  // ② 4項目の構成比とそれぞれの伸び
  const drugPct         = (growth.drugGrowth         - 1) * 100;
  const pharmMgmtPct    = (growth.pharmMgmtGrowth    - 1) * 100;
  const dispenseTechPct = (growth.dispenseTechGrowth - 1) * 100;
  const medMaterialPct  = (growth.medMaterialGrowth  - 1) * 100;
  const growthMap = {
    drugFee: drugPct, pharmMgmtFee: pharmMgmtPct,
    dispenseTechFee: dispenseTechPct, medMaterialFee: medMaterialPct,
  };
  const compositionStr = sortedByRatio
    .map(r => `${r.label} ${fmt(r.ratio, 1)}%`).join(' / ');
  // 伸び率トップ・ワースト
  const growthRanking = FEE_KEYS
    .map(k => ({ key: k, label: FEE_LABELS[k], pct: growthMap[k] }))
    .sort((a, b) => b.pct - a.pct);
  const topGrower = growthRanking[0];
  const worstGrower = growthRanking[growthRanking.length - 1];
  let comp = '';
  if (topGrower.pct - worstGrower.pct >= 5) {
    comp = `4項目の中では ${topGrower.label}（${topGrower.pct >= 0 ? '+' : ''}${fmt(topGrower.pct, 1)}%）が最も伸び、${worstGrower.label}（${worstGrower.pct >= 0 ? '+' : ''}${fmt(worstGrower.pct, 1)}%）が伸び悩みました。項目間の差は ${fmt(topGrower.pct - worstGrower.pct, 1)}pt と明確で、収益ドライバーが特定の項目に偏っています。`;
  } else {
    comp = `4項目の伸び率はいずれも近接（${worstGrower.pct >= 0 ? '+' : ''}${fmt(worstGrower.pct, 1)}% 〜 ${topGrower.pct >= 0 ? '+' : ''}${fmt(topGrower.pct, 1)}%）し、バランス型の推移です。`;
  }
  paragraphs.push(
    `直近月の売上構成は ${compositionStr}。最大シェアは ${topItem.label}（${fmt(topItem.ratio, 1)}%）、最小シェアは ${bottomItem.label}（${fmt(bottomItem.ratio, 1)}%）。${comp}`
  );

  // ③ ピーク・ボトム + ボラティリティ
  let volNote = '';
  if (cv < 5) volNote = '月次ブレは小さく、安定した運営です。';
  else if (cv < 10) volNote = '月次ブレは標準的な範囲内です。';
  else if (cv < 15) volNote = '月次ブレがやや大きく、季節要因や処方シーズンの影響が見られます。';
  else volNote = '月次ブレが大きく、特定月の要因分析が望まれます。';
  paragraphs.push(
    `期間中のピークは ${maxRow.month}（${fmt(maxRow.total)} 万円）、ボトムは ${minRow.month}（${fmt(minRow.total)} 万円）。` +
    `1日あたり売上は ${maxDayRow.month} に ${fmt1(maxDayRow.totalPerDay)} 万円/日 と最大、${minDayRow.month} に ${fmt1(minDayRow.totalPerDay)} 万円/日 と最小でした。` +
    `変動係数 ${fmt(cv, 1)}% 。${volNote}`
  );

  // ④ 直近トレンド
  const perDayPct = (growth.perDayGrowth - 1) * 100;
  let trend = '';
  if (recentDelta >= 5) {
    trend = `直近3ヶ月の1日売上平均は ${fmt1(recentAvg)} 万円/日 で、期間前半比 +${fmt(recentDelta, 1)}% と加速しています。`;
  } else if (recentDelta <= -5) {
    trend = `直近3ヶ月の1日売上平均は ${fmt1(recentAvg)} 万円/日 で、期間前半比 ${fmt(recentDelta, 1)}% と減速傾向。営業日数や処方獲得の点検が望まれます。`;
  } else {
    trend = `直近3ヶ月の1日売上平均は ${fmt1(recentAvg)} 万円/日 で、期間前半とほぼ同水準の巡航状態です。`;
  }
  trend += `1日あたり売上の成長率は ×${fmt(growth.perDayGrowth, 2)}（${perDayPct >= 0 ? '+' : ''}${fmt(perDayPct, 1)}%）で、生産性面では${perDayPct >= 0 ? 'プラス寄与' : '改善余地あり'}です。`;
  paragraphs.push(trend);

  // ⑤ 在庫と運転資金
  const lastInv  = last.inventoryAmount || 0;
  const firstInv = first.inventoryAmount || 0;
  let invPara = '';
  let invMoodFlag = 'good'; // 'good' | 'warn' | 'bad' | 'neutral'
  if (lastInv === 0 && firstInv === 0) {
    invPara = '在庫金額の入力がないため、在庫健全度の評価は割愛します。各月の月末在庫金額をご入力いただくと、運転資金観点での診断が可能になります。';
    invMoodFlag = 'neutral';
  } else {
    const invPct = firstInv > 0 ? (lastInv / firstInv - 1) * 100 : 0;
    const drugPctLocal = (growth.drugGrowth - 1) * 100;
    let level = `直近月の在庫金額は ${fmt(lastInv)} 万円 です。`;
    let consistency = '';
    if (firstInv > 0) {
      if (invPct - drugPctLocal > 10) {
        consistency = `期間中、在庫金額は ${invPct >= 0 ? '+' : ''}${fmt(invPct, 1)}% 推移に対し薬剤料は ${drugPctLocal >= 0 ? '+' : ''}${fmt(drugPctLocal, 1)}% で、売上の伸び以上に在庫が積み上がっています。運転資金圧迫要因の精査が必要です。`;
        invMoodFlag = 'warn';
      } else if (drugPctLocal - invPct > 10) {
        consistency = `期間中、在庫金額は ${invPct >= 0 ? '+' : ''}${fmt(invPct, 1)}% に対し薬剤料は ${drugPctLocal >= 0 ? '+' : ''}${fmt(drugPctLocal, 1)}% と、売上拡大に対して在庫が抑制されており、運転資金効率は改善傾向です。`;
        invMoodFlag = 'good';
      } else {
        consistency = `在庫金額は ${invPct >= 0 ? '+' : ''}${fmt(invPct, 1)}%、薬剤料は ${drugPctLocal >= 0 ? '+' : ''}${fmt(drugPctLocal, 1)}% と概ね整合的に推移しています。`;
        invMoodFlag = 'good';
      }
    }
    invPara = `${level}${consistency}`;
  }
  paragraphs.push(invPara);

  // ⑥ 経営者向けコメント・推奨アクション
  let advice = '';
  const invHint = invMoodFlag === 'bad'
    ? '在庫圧縮（不動在庫整理・分割発注・卸への返品交渉）'
    : invMoodFlag === 'warn'
      ? '在庫金額の適正化（発注ロット見直し）'
      : '在庫の最適化';
  if (mood === 'good') {
    advice = `【推奨アクション】堅調な拡大局面を活かし、(1) 高成長要因の他店舗展開、(2) 人員増強による処方箋応需上限の引き上げ、(3) ${invHint}、を検討すべきタイミングです。`;
  } else if (mood === 'neutral') {
    advice = `【推奨アクション】横ばい局面では、(1) 薬剤師1人あたり生産性、(2) 患者あたり技術料単価、(3) ${invHint}と運転資金効率、の3指標を深掘りし、次の成長ドライバーを特定することをおすすめします。`;
  } else if (mood === 'warn') {
    advice = `【推奨アクション】減速傾向の早期是正のため、(1) 主要処方医療機関の動向確認、(2) かかりつけ薬剤師指導料など算定漏れの点検、(3) ${invHint}、をご検討ください。`;
  } else {
    advice = `【推奨アクション】要警戒水準のため、(1) 月次の要因分解レビュー、(2) 営業日確保と人員配置の最適化、(3) ${invHint}と法人内他店ノウハウ移転、を優先タスクとして実行をおすすめします。`;
  }
  paragraphs.push(advice);

  // 在庫の総合判定をmoodに反映（売上が良くても在庫過剰なら警戒）
  if (mood === 'good' && invMoodFlag === 'bad') mood = 'warn';
  if (mood === 'neutral' && invMoodFlag === 'bad') mood = 'warn';

  /* ---- タグ ---- */
  const tags = [];
  if (totalPct >= 10) tags.push({ label: '成長', color: COLORS.green });
  else if (totalPct <= -5) tags.push({ label: '要注意', color: COLORS.red });
  else tags.push({ label: '安定', color: COLORS.blue });

  // シェア最大項目をリードとしてタグ化
  if (topItem && topItem.ratio >= 40) {
    tags.push({ label: `${topItem.label}リード`, color: FEE_COLORS[topItem.key] });
  }
  // 4項目の中で突出して伸びた項目があればタグ化
  if (topGrower && topGrower.pct - worstGrower.pct >= 5 && topGrower.pct >= 5) {
    tags.push({ label: `${topGrower.label}牽引`, color: FEE_COLORS[topGrower.key] });
  }

  if (cv >= 15) tags.push({ label: '変動大', color: COLORS.red });
  else if (cv < 5) tags.push({ label: '安定運営', color: COLORS.green });

  if (perDayPct >= 5) tags.push({ label: '生産性向上', color: COLORS.green });
  else if (perDayPct <= -5) tags.push({ label: '生産性低下', color: COLORS.red });

  // 在庫タグ（在庫金額の対薬剤料連動性ベース）
  if (lastInv > 0) {
    if (invMoodFlag === 'warn') tags.push({ label: '在庫増加', color: COLORS.orange });
    else if (invMoodFlag === 'good') tags.push({ label: '在庫健全', color: '#7C3AED' });
  }

  return { headline, paragraphs, tags, mood };
}

/* 比較モード用の AI 総評 */
function generateCompareInsight(seriesList) {
  if (seriesList.length === 0) {
    return { headline: '比較対象が選択されていません', paragraphs: ['比較したい店舗・法人をチェックしてください。'], tags: [] };
  }
  if (seriesList.length === 1) {
    return generateInsight(seriesList[0].rows, seriesList[0].growth, { fullName: seriesList[0].name });
  }

  // 最終月総売上ランキング
  const ranked = seriesList
    .filter(s => s.rows.length > 0)
    .map(s => ({
      ...s,
      lastTotal: s.rows[s.rows.length - 1]?.total || 0,
      lastPerDay: s.rows[s.rows.length - 1]?.totalPerDay || 0,
      growthPct: (s.growth.totalGrowth - 1) * 100,
      perDayGrowthPct: (s.growth.perDayGrowth - 1) * 100,
    }))
    .sort((a, b) => b.lastTotal - a.lastTotal);

  if (ranked.length === 0) {
    return { headline: 'データのある対象がありません', paragraphs: ['選択した対象にデータが存在しません。'], tags: [] };
  }

  const top = ranked[0];
  const bottom = ranked[ranked.length - 1];
  const byGrowth = [...ranked].sort((a, b) => b.growthPct - a.growthPct);
  const fastest = byGrowth[0];
  const slowest = byGrowth[byGrowth.length - 1];
  const byPerDay = [...ranked].sort((a, b) => b.lastPerDay - a.lastPerDay);
  const efficiencyTop = byPerDay[0];

  const headline =
    `比較対象 ${ranked.length}件のうち、直近月の売上規模では「${top.name}」が ${fmt(top.lastTotal)} 万円で首位、` +
    `成長率では「${fastest.name}」が +${fmt(fastest.growthPct, 1)}% でトップです。`;

  const paragraphs = [];

  // 規模感比較
  const totalAll = ranked.reduce((a, b) => a + b.lastTotal, 0);
  paragraphs.push(
    `直近月の合計売上は ${fmt(totalAll)} 万円。内訳は ` +
    ranked.map(r => `${r.name} ${fmt(r.lastTotal)}万円（${fmt((r.lastTotal/totalAll)*100, 1)}%）`).join(' / ') +
    ` でした。`
  );

  // 成長率比較
  paragraphs.push(
    `成長率では ${byGrowth.map(r => `${r.name} ${r.growthPct >= 0 ? '+' : ''}${fmt(r.growthPct, 1)}%`).join(' / ')}。` +
    (fastest.growthPct - slowest.growthPct > 10
      ? `成長スピードに大きな差があり、「${fastest.name}」のノウハウを「${slowest.name}」に展開する余地があります。`
      : `各対象の成長率は概ね揃っており、グループとして安定した推移です。`)
  );

  // 1日あたり生産性
  paragraphs.push(
    `1日あたり売上（直近月）では「${efficiencyTop.name}」が ${fmt1(efficiencyTop.lastPerDay)} 万円/日 で最高効率。` +
    `最低の「${byPerDay[byPerDay.length - 1].name}」（${fmt1(byPerDay[byPerDay.length - 1].lastPerDay)} 万円/日）との差は ` +
    `${fmt1(efficiencyTop.lastPerDay - byPerDay[byPerDay.length - 1].lastPerDay)} 万円/日 で、` +
    `生産性ギャップ${(efficiencyTop.lastPerDay / Math.max(0.01, byPerDay[byPerDay.length - 1].lastPerDay)) >= 1.3 ? 'は大きく、配置・体制の見直し余地があります' : 'は許容範囲内です'}。`
  );

  // 推奨アクション
  paragraphs.push(
    `【推奨アクション】(1)「${fastest.name}」の好調要因（処方獲得・指導料算定・人員配置）を言語化し、(2) 全社で横展開、` +
    `(3)「${slowest.name}」については営業日数・処方箋枚数・単価の3軸で要因分解レビューを行うことをおすすめします。`
  );

  const tags = [
    { label: `${ranked.length}件比較`, color: COLORS.blue },
    { label: `首位:${top.name}`, color: COLORS.green },
    { label: `成長率トップ:${fastest.name}`, color: COLORS.orange },
  ];

  return { headline, paragraphs, tags, mood: 'neutral' };
}

/* =====================================================================
   AI 総評（OpenAI連携）— /api/insight をSSEで叩いて逐次表示
   ===================================================================== */
function buildInsightPayload(rows, growth, label) {
  // 数値はそのまま、月次配列を簡潔な形でサーバへ
  const monthly = rows.map(r => ({
    month: r.month,
    workingDays: r.workingDays,
    drugFee:         Math.round(r.drugFee         * 10) / 10,
    pharmMgmtFee:    Math.round(r.pharmMgmtFee    * 10) / 10,
    dispenseTechFee: Math.round(r.dispenseTechFee * 10) / 10,
    medMaterialFee:  Math.round(r.medMaterialFee  * 10) / 10,
    total:           Math.round(r.total           * 10) / 10,
    drugPerDay:         Math.round(r.drugPerDay         * 100) / 100,
    pharmMgmtPerDay:    Math.round(r.pharmMgmtPerDay    * 100) / 100,
    dispenseTechPerDay: Math.round(r.dispenseTechPerDay * 100) / 100,
    medMaterialPerDay:  Math.round(r.medMaterialPerDay  * 100) / 100,
    totalPerDay:        Math.round(r.totalPerDay        * 100) / 100,
    inventoryAmount: Math.round((r.inventoryAmount || 0) * 10) / 10,
  }));
  const last = rows.length > 0 ? rows[rows.length - 1] : null;
  const first = rows.length > 0 ? rows[0] : null;
  const summary = rows.length > 0 ? {
    periodFrom: rows[0].month,
    periodTo:   rows[rows.length - 1].month,
    totalSum:   Math.round(rows.reduce((a, b) => a + b.total, 0) * 10) / 10,
    avgPerDay:  (() => {
      const days = rows.reduce((a, b) => a + (b.workingDays || 0), 0);
      const sum  = rows.reduce((a, b) => a + b.total, 0);
      return days > 0 ? Math.round((sum / days) * 100) / 100 : 0;
    })(),
    composition: last ? {
      drugRatio:         last.total > 0 ? Math.round((last.drugFee         / last.total) * 1000) / 10 : 0,
      pharmMgmtRatio:    last.total > 0 ? Math.round((last.pharmMgmtFee    / last.total) * 1000) / 10 : 0,
      dispenseTechRatio: last.total > 0 ? Math.round((last.dispenseTechFee / last.total) * 1000) / 10 : 0,
      medMaterialRatio:  last.total > 0 ? Math.round((last.medMaterialFee  / last.total) * 1000) / 10 : 0,
    } : null,
    inventory: {
      latestAmount: Math.round((last?.inventoryAmount  || 0) * 10) / 10,
      firstAmount:  Math.round((first?.inventoryAmount || 0) * 10) / 10,
    },
    growth: {
      drug:         Math.round((growth?.drugGrowth         ?? 1) * 1000) / 1000,
      pharmMgmt:    Math.round((growth?.pharmMgmtGrowth    ?? 1) * 1000) / 1000,
      dispenseTech: Math.round((growth?.dispenseTechGrowth ?? 1) * 1000) / 1000,
      medMaterial:  Math.round((growth?.medMaterialGrowth  ?? 1) * 1000) / 1000,
      total:        Math.round((growth?.totalGrowth        ?? 1) * 1000) / 1000,
      perDay:       Math.round((growth?.perDayGrowth       ?? 1) * 1000) / 1000,
      inventory:    Math.round((growth?.inventoryGrowth    ?? 1) * 1000) / 1000,
    },
  } : null;
  return { label, unit: '万円', summary, monthly };
}

function buildComparePayload(seriesList) {
  return {
    items: seriesList.map(s => ({
      name: s.fullName || s.name,
      kind: s.kind,
      ...buildInsightPayload(s.rows, s.growth, s.fullName || s.name),
    })),
  };
}

/* SSE経由でOpenAIストリーミングを受け取るフック */
function useAIInsight() {
  const [text, setText]       = useState('');
  const [status, setStatus]   = useState('idle'); // 'idle' | 'loading' | 'streaming' | 'done' | 'error'
  const [error, setError]     = useState(null);
  const abortRef              = useRef(null);

  const reset = useCallback(() => {
    if (abortRef.current) { try { abortRef.current.abort(); } catch {} }
    abortRef.current = null;
    setText(''); setStatus('idle'); setError(null);
  }, []);

  const run = useCallback(async ({ mode, payload, target }) => {
    if (abortRef.current) { try { abortRef.current.abort(); } catch {} }
    const ac = new AbortController();
    abortRef.current = ac;
    setText(''); setError(null); setStatus('loading');

    try {
      const res = await fetch('/api/insight', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mode, payload, target }),
        signal: ac.signal,
      });

      if (!res.ok) {
        let detail = '';
        try { const j = await res.json(); detail = j.error || JSON.stringify(j); } catch { detail = await res.text().catch(() => ''); }
        throw new Error(`HTTP ${res.status} ${detail || ''}`.trim());
      }
      if (!res.body) throw new Error('No response body');

      setStatus('streaming');
      const reader  = res.body.getReader();
      const decoder = new TextDecoder('utf-8');
      let buffer = '';
      let acc = '';

      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });

        // SSE: イベントは "\n\n" 区切り、データ行は "data: ..."
        let idx;
        while ((idx = buffer.indexOf('\n\n')) >= 0) {
          const event = buffer.slice(0, idx);
          buffer = buffer.slice(idx + 2);
          for (const line of event.split('\n')) {
            const m = line.match(/^data:\s?(.*)$/);
            if (!m) continue;
            const data = m[1];
            if (data === '[DONE]') { /* 上流完了 */ continue; }
            try {
              const j = JSON.parse(data);
              const delta = j?.choices?.[0]?.delta?.content
                          ?? j?.choices?.[0]?.message?.content
                          ?? '';
              if (delta) { acc += delta; setText(acc); }
            } catch {
              // OpenAI互換以外のフラグメントが来た場合は素通し
            }
          }
        }
      }
      setStatus('done');
    } catch (e) {
      if (e?.name === 'AbortError') { setStatus('idle'); return; }
      setError(e?.message || String(e));
      setStatus('error');
    }
  }, []);

  const stop = useCallback(() => {
    if (abortRef.current) { try { abortRef.current.abort(); } catch {} }
    setStatus('idle');
  }, []);

  return { text, status, error, run, reset, stop };
}

/* マークダウン超軽量レンダラ（## 見出しと段落のみ対応） */
function renderMarkdown(md) {
  if (!md) return null;
  const blocks = md.split(/\n{2,}/).map(b => b.trim()).filter(Boolean);
  return blocks.map((b, i) => {
    const h2 = b.match(/^##\s+(.+)$/);
    if (h2) {
      return <h4 key={i} className="ai-md-h2">{h2[1]}</h4>;
    }
    return <p key={i} className="ai-md-p">{b}</p>;
  });
}

/* =====================================================================
   トースト
   ===================================================================== */
function useToast() {
  const [msg, setMsg] = useState(null);
  const show = useCallback((m) => { setMsg(m); setTimeout(() => setMsg(null), 2800); }, []);
  const node = msg ? <div className="toast">{msg}</div> : null;
  return [node, show];
}

/* =====================================================================
   ヘッダー
   ===================================================================== */
function Header({ mode, setMode, view, setView }) {
  return (
    <header className="no-print bg-white border-b border-brand-border">
      <div className="max-w-[1920px] mx-auto px-8 py-4 flex items-center justify-between gap-6 flex-wrap">
        <div className="flex items-center gap-4">
          <img src="/static/logo.png" alt="IPB Logo" className="h-12 w-auto" />
          <div className="hidden md:block border-l border-brand-border pl-4">
            <div className="text-xs text-gray-500 font-medium">経営者向け 月別売上推移ダッシュボード</div>
            <div className="text-sm font-bold text-brand-text">
              Iwaki Pharma Board / Pharmacy Insight Dashboard
            </div>
          </div>
        </div>
        <div className="flex items-center gap-3 flex-wrap">
          {/* ビュー切替（ダッシュボード／データ入力） */}
          <div className="mode-switch">
            <button
              className={`mode-btn ${view === 'dashboard' ? 'mode-btn-active' : ''}`}
              onClick={() => setView('dashboard')}
              title="ダッシュボード（グラフ・KPI・AI総評）"
            >
              <i className="fas fa-chart-line mr-1.5"></i>ダッシュボード
            </button>
            <button
              className={`mode-btn ${view === 'input' ? 'mode-btn-active' : ''}`}
              onClick={() => setView('input')}
              title="データ入力（月次の数値入力・CSV取込）"
            >
              <i className="fas fa-keyboard mr-1.5"></i>データ入力
            </button>
          </div>
          {/* モード切替 */}
          <div className="mode-switch">
            <button
              className={`mode-btn ${mode === 'single' ? 'mode-btn-active' : ''}`}
              onClick={() => setMode('single')}
            >
              <i className="fas fa-chart-column mr-1.5"></i>単独表示
            </button>
            <button
              className={`mode-btn ${mode === 'compare' ? 'mode-btn-active' : ''}`}
              onClick={() => { setMode('compare'); setView('dashboard'); }}
            >
              <i className="fas fa-code-compare mr-1.5"></i>比較モード
            </button>
          </div>
          <div className="text-right hidden lg:block">
            <div className="text-[11px] text-gray-500">運営法人</div>
            <div className="text-xs font-bold text-brand-text">いわ木 / シンシアー</div>
          </div>
        </div>
      </div>
    </header>
  );
}

/* =====================================================================
   タブバー（単独モード用）
   ===================================================================== */
function TabBar({ activeTab, setActiveTab }) {
  const groups = [
    { title: '店舗別',       items: TABS.filter(t => t.kind === 'store') },
    { title: '法人別 合計',  items: TABS.filter(t => t.kind === 'company') },
    { title: '全社合計',     items: TABS.filter(t => t.kind === 'all') },
  ];
  return (
    <div className="no-print bg-white border-b border-brand-border sticky top-0 z-20">
      <div className="max-w-[1920px] mx-auto px-6 md:px-10 py-3 flex flex-wrap items-center gap-4 overflow-x-auto scrollbar-thin">
        {groups.map((g, gi) => (
          <React.Fragment key={g.title}>
            {gi > 0 && <div className="h-7 w-px bg-brand-border mx-1"></div>}
            <div className="flex items-center gap-2">
              <span className="text-[10px] font-bold tracking-wider text-gray-400 uppercase whitespace-nowrap">
                {g.title}
              </span>
              <div className="flex items-center gap-1.5">
                {g.items.map(t => {
                  const active = activeTab === t.id;
                  return (
                    <button
                      key={t.id}
                      onClick={() => setActiveTab(t.id)}
                      className={`tab-btn ${active ? 'tab-btn-active' : ''} ${t.kind === 'company' ? 'tab-btn-company' : ''} ${t.kind === 'all' ? 'tab-btn-all' : ''}`}
                      title={t.fullName}
                    >
                      <i className={`fas ${t.icon} text-[11px] mr-1.5`}></i>
                      {t.label}
                    </button>
                  );
                })}
              </div>
            </div>
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

/* =====================================================================
   比較選択バー（比較モード用）
   ===================================================================== */
function CompareSelector({ selectedIds, setSelectedIds }) {
  const groups = [
    { title: '店舗別',      items: TABS.filter(t => t.kind === 'store') },
    { title: '法人別 合計', items: TABS.filter(t => t.kind === 'company') },
    { title: '全社合計',    items: TABS.filter(t => t.kind === 'all') },
  ];
  const toggle = (id) => {
    if (selectedIds.includes(id)) setSelectedIds(selectedIds.filter(x => x !== id));
    else setSelectedIds([...selectedIds, id]);
  };
  const selectStores  = () => setSelectedIds(STORES.map(s => s.id));
  const selectCompanies = () => setSelectedIds(COMPANIES.map(c => c.id));
  const clearAll      = () => setSelectedIds([]);

  return (
    <div className="no-print bg-white border-b border-brand-border sticky top-0 z-20">
      <div className="max-w-[1920px] mx-auto px-6 md:px-10 py-3">
        <div className="flex items-center gap-3 mb-2 flex-wrap">
          <span className="text-[11px] font-bold tracking-wider text-gray-500 uppercase">
            <i className="fas fa-layer-group mr-1"></i>比較対象を選択
          </span>
          <span className="text-[11px] text-gray-500">
            {selectedIds.length} 件選択中
          </span>
          <div className="flex-1"></div>
          <button className="btn btn-secondary !py-1.5 !text-[12px]" onClick={selectStores}>
            <i className="fas fa-prescription-bottle-medical"></i>5店舗を全選択
          </button>
          <button className="btn btn-secondary !py-1.5 !text-[12px]" onClick={selectCompanies}>
            <i className="fas fa-building"></i>2法人を全選択
          </button>
          <button className="btn btn-danger !py-1.5 !text-[12px]" onClick={clearAll}>
            <i className="fas fa-times"></i>解除
          </button>
        </div>
        <div className="flex flex-wrap items-center gap-4">
          {groups.map((g, gi) => (
            <React.Fragment key={g.title}>
              {gi > 0 && <div className="h-7 w-px bg-brand-border"></div>}
              <div className="flex items-center gap-2 flex-wrap">
                <span className="text-[10px] font-bold tracking-wider text-gray-400 uppercase whitespace-nowrap">
                  {g.title}
                </span>
                {g.items.map(t => {
                  const idx = selectedIds.indexOf(t.id);
                  const active = idx >= 0;
                  const seriesColor = active ? SERIES_COLORS[idx % SERIES_COLORS.length] : null;
                  return (
                    <label
                      key={t.id}
                      className={`compare-chip ${active ? 'compare-chip-active' : ''}`}
                      style={active ? { borderColor: seriesColor, background: `${seriesColor}1A`, color: seriesColor } : {}}
                    >
                      <input
                        type="checkbox"
                        className="hidden"
                        checked={active}
                        onChange={() => toggle(t.id)}
                      />
                      {active && <span className="series-dot" style={{ background: seriesColor }}></span>}
                      <i className={`fas ${t.icon} text-[10px] mr-1`}></i>
                      {t.label}
                    </label>
                  );
                })}
              </div>
            </React.Fragment>
          ))}
        </div>
      </div>
    </div>
  );
}

/* =====================================================================
   入力パネル
   ===================================================================== */
function InputPanel({ data, onAddRow, onRemoveRow, onUpdateRow, onImportCSV, onLoadSample, onClear }) {
  const fileRef = useRef(null);
  const handleFile = (e) => {
    const f = e.target.files?.[0]; if (!f) return;
    const reader = new FileReader();
    reader.onload = (ev) => onImportCSV(ev.target.result);
    reader.readAsText(f, 'UTF-8');
    e.target.value = '';
  };
  return (
    <div className="no-print dash-card">
      <div className="flex items-center justify-between mb-4 flex-wrap gap-2">
        <div className="dash-card-title mb-0">
          <span className="badge"><i className="fas fa-edit text-[11px]"></i></span>
          月別データ入力
        </div>
        <div className="text-xs text-gray-500">
          {data.length} 件 / 入力データはブラウザに自動保存されます
        </div>
      </div>
      <div className="overflow-x-auto scrollbar-thin">
        <table className="ipb-table" style={{ minWidth: 1000 }}>
          <thead>
            <tr>
              <th style={{ width: 40 }}>#</th>
              <th>月 (YYYY-MM)</th>
              <th>営業日数</th>
              <th style={{ color: FEE_COLORS.drugFee }}>薬剤料 (万円)</th>
              <th style={{ color: FEE_COLORS.pharmMgmtFee }}>薬学管理料 (万円)</th>
              <th style={{ color: FEE_COLORS.dispenseTechFee }}>調剤技術料 (万円)</th>
              <th style={{ color: FEE_COLORS.medMaterialFee }}>医療材料料 (万円)</th>
              <th className="text-right">合計 (万円)</th>
              <th>在庫金額 (万円)</th>
              <th style={{ width: 70 }}></th>
            </tr>
          </thead>
          <tbody>
            {data.map((row, i) => {
              const drugFee         = Number(row.drugFee)         || 0;
              const pharmMgmtFee    = Number(row.pharmMgmtFee)    || 0;
              const dispenseTechFee = Number(row.dispenseTechFee) || 0;
              const medMaterialFee  = Number(row.medMaterialFee)  || 0;
              const total = drugFee + pharmMgmtFee + dispenseTechFee + medMaterialFee;
              return (
                <tr key={i}>
                  <td className="text-gray-400 num">{i + 1}</td>
                  <td><input type="month" className="ipb-input" value={row.month} onChange={(e) => onUpdateRow(i, 'month', e.target.value)} /></td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.workingDays} onChange={(e) => onUpdateRow(i, 'workingDays', Number(e.target.value))} /></td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.drugFee ?? 0}         onChange={(e) => onUpdateRow(i, 'drugFee',         Number(e.target.value))} /></td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.pharmMgmtFee ?? 0}    onChange={(e) => onUpdateRow(i, 'pharmMgmtFee',    Number(e.target.value))} /></td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.dispenseTechFee ?? 0} onChange={(e) => onUpdateRow(i, 'dispenseTechFee', Number(e.target.value))} /></td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.medMaterialFee ?? 0}  onChange={(e) => onUpdateRow(i, 'medMaterialFee',  Number(e.target.value))} /></td>
                  <td className="num font-num font-semibold" style={{ color: COLORS.green }}>{fmt(total)}</td>
                  <td><input type="number" min="0" className="ipb-input num" value={row.inventoryAmount ?? 0} onChange={(e) => onUpdateRow(i, 'inventoryAmount', Number(e.target.value))} /></td>
                  <td><button onClick={() => onRemoveRow(i)} className="text-brand-red hover:bg-red-50 rounded p-2" title="この行を削除"><i className="fas fa-trash"></i></button></td>
                </tr>
              );
            })}
            {data.length === 0 && (<tr><td colSpan="10" className="text-center text-gray-400 py-6">データがありません。「行を追加」または「サンプル読込」をご利用ください。</td></tr>)}
          </tbody>
        </table>
      </div>
      <div className="mt-4 flex flex-wrap items-center gap-2">
        <button className="btn btn-secondary" onClick={onAddRow}><i className="fas fa-plus"></i>行を追加</button>
        <button className="btn btn-secondary" onClick={() => fileRef.current?.click()}><i className="fas fa-file-csv"></i>CSVインポート</button>
        <input ref={fileRef} type="file" accept=".csv,text/csv" className="hidden" onChange={handleFile} />
        <button className="btn btn-secondary" onClick={onLoadSample}><i className="fas fa-flask"></i>この店舗のサンプル読込</button>
        <button className="btn btn-danger" onClick={onClear}><i className="fas fa-eraser"></i>この店舗を全削除</button>
      </div>
      <div className="mt-3 text-[11px] text-gray-500">
        <i className="fas fa-info-circle mr-1"></i>
        CSVヘッダー:
        <code className="px-1.5 py-0.5 bg-gray-100 rounded mx-1">month,workingDays,drugFee,pharmMgmtFee,dispenseTechFee,medMaterialFee,inventoryAmount</code>
        （UTF-8 / カンマ区切り。inventoryAmount は任意。旧形式 <code className="px-1 bg-gray-100 rounded">techFee,drugFee</code> も自動変換します）
      </div>
    </div>
  );
}

/* =====================================================================
   集計パネル（読み取り専用）
   ===================================================================== */
function AggregatePanel({ tab, rows, sourceStores }) {
  return (
    <div className="no-print dash-card">
      <div className="flex items-center justify-between mb-4 flex-wrap gap-2">
        <div className="dash-card-title mb-0">
          <span className="badge"><i className="fas fa-calculator text-[11px]"></i></span>
          {tab.fullName}（自動集計）
        </div>
        <div className="text-xs text-gray-500">集計対象: {sourceStores.map(s => s.short).join(' + ')}</div>
      </div>
      <div className="text-[12px] text-gray-600 mb-2">
        <i className="fas fa-info-circle mr-1 text-brand-blue"></i>
        各店舗のデータを月ごとに合算しています。
        <span className="text-gray-500">薬剤料・薬学管理料・調剤技術料・医療材料料・在庫金額は合計、営業日数は対象店舗の最大値を採用。</span>
        編集は各店舗タブで行ってください。
      </div>
      {rows.length === 0 ? (
        <div className="text-center text-gray-400 py-6">対象店舗にデータがありません。</div>
      ) : (
        <div className="overflow-x-auto scrollbar-thin" style={{ maxHeight: 280 }}>
          <table className="ipb-table" style={{ minWidth: 900 }}>
            <thead><tr>
              <th>月</th>
              <th className="text-right">営業日(最大)</th>
              <th className="text-right" style={{ color: FEE_COLORS.drugFee }}>薬剤料</th>
              <th className="text-right" style={{ color: FEE_COLORS.pharmMgmtFee }}>薬学管理料</th>
              <th className="text-right" style={{ color: FEE_COLORS.dispenseTechFee }}>調剤技術料</th>
              <th className="text-right" style={{ color: FEE_COLORS.medMaterialFee }}>医療材料料</th>
              <th className="text-right">合計売上</th>
              <th className="text-right">在庫金額 合計</th>
            </tr></thead>
            <tbody>
              {rows.map((r, i) => {
                const inv = Number(r.inventoryAmount) || 0;
                const total = (r.drugFee || 0) + (r.pharmMgmtFee || 0) + (r.dispenseTechFee || 0) + (r.medMaterialFee || 0);
                return (
                  <tr key={i}>
                    <td className="font-semibold">{r.month}</td>
                    <td className="num">{fmt(r.workingDays)}</td>
                    <td className="num" style={{ color: FEE_COLORS.drugFee }}>{fmt(r.drugFee)}</td>
                    <td className="num" style={{ color: FEE_COLORS.pharmMgmtFee }}>{fmt(r.pharmMgmtFee)}</td>
                    <td className="num" style={{ color: FEE_COLORS.dispenseTechFee }}>{fmt(r.dispenseTechFee)}</td>
                    <td className="num" style={{ color: FEE_COLORS.medMaterialFee }}>{fmt(r.medMaterialFee)}</td>
                    <td className="num font-bold" style={{ color: COLORS.green }}>{fmt(total)}</td>
                    <td className="num" style={{ color: COLORS.purple }}>{fmt(inv)}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* =====================================================================
   各種チャート
   ===================================================================== */
function ChartCombo({ rows }) {
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title">
        <span className="badge">1</span>月別 売上構成（薬剤料 / 薬学管理料 / 調剤技術料 / 医療材料料 / 合計）
        <span className="ml-auto text-[11px] font-normal text-gray-500">単位：万円</span>
      </div>
      <ResponsiveContainer width="100%" height={360}>
        <ComposedChart data={rows} margin={{ top: 20, right: 24, left: 0, bottom: 8 }} barCategoryGap="18%" barGap={2}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEF2F7" />
          <XAxis dataKey="month" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
          <YAxis tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} />
          <Tooltip formatter={(v, name) => [`${fmt(v)} 万円`, name]} contentStyle={{ borderRadius: 8, border: '1px solid #E5E7EB', fontSize: 12 }} />
          <Legend wrapperStyle={{ paddingTop: 8 }} />
          <Bar dataKey="drugFee"         name="薬剤料"     fill={FEE_COLORS.drugFee}         radius={[4, 4, 0, 0]} barSize={10} />
          <Bar dataKey="pharmMgmtFee"    name="薬学管理料" fill={FEE_COLORS.pharmMgmtFee}    radius={[4, 4, 0, 0]} barSize={10} />
          <Bar dataKey="dispenseTechFee" name="調剤技術料" fill={FEE_COLORS.dispenseTechFee} radius={[4, 4, 0, 0]} barSize={10} />
          <Bar dataKey="medMaterialFee"  name="医療材料料" fill={FEE_COLORS.medMaterialFee}  radius={[4, 4, 0, 0]} barSize={10} />
          <Line dataKey="total" name="合計売上" stroke={COLORS.green} strokeWidth={3} type="monotone" dot={{ r: 4, fill: COLORS.green }} activeDot={{ r: 6 }} />
        </ComposedChart>
      </ResponsiveContainer>
    </div>
  );
}
function DataTable({ rows }) {
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title"><span className="badge">2</span>月別データ（明細）</div>
      <div className="overflow-x-auto scrollbar-thin" style={{ maxHeight: 360 }}>
        <table className="ipb-table" style={{ minWidth: 980 }}>
          <thead><tr>
            <th>月</th>
            <th className="text-right">営業日</th>
            <th className="text-right" style={{ color: FEE_COLORS.drugFee }}>薬剤料</th>
            <th className="text-right" style={{ color: FEE_COLORS.pharmMgmtFee }}>薬学管理料</th>
            <th className="text-right" style={{ color: FEE_COLORS.dispenseTechFee }}>調剤技術料</th>
            <th className="text-right" style={{ color: FEE_COLORS.medMaterialFee }}>医療材料料</th>
            <th className="text-right">合計</th>
            <th className="text-right">合計/日</th>
            <th className="text-right">在庫金額</th>
          </tr></thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={i}>
                <td className="font-semibold">{r.month}</td>
                <td className="num">{fmt(r.workingDays)}</td>
                <td className="num" style={{ color: FEE_COLORS.drugFee }}>{fmt(r.drugFee)}</td>
                <td className="num" style={{ color: FEE_COLORS.pharmMgmtFee }}>{fmt(r.pharmMgmtFee)}</td>
                <td className="num" style={{ color: FEE_COLORS.dispenseTechFee }}>{fmt(r.dispenseTechFee)}</td>
                <td className="num" style={{ color: FEE_COLORS.medMaterialFee }}>{fmt(r.medMaterialFee)}</td>
                <td className="num font-bold" style={{ color: COLORS.green }}>{fmt(r.total)}</td>
                <td className="num font-semibold">{fmt1(r.totalPerDay)}</td>
                <td className="num" style={{ color: COLORS.purple }}>{fmt(r.inventoryAmount)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="mt-2 text-[11px] text-gray-500 text-right">単位：万円（合計/日 = 合計 ÷ 営業日）</div>
    </div>
  );
}
function ChartTotalDay({ rows }) {
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title"><span className="badge">3</span>1日あたり 合計売上推移
        <span className="ml-auto text-[11px] font-normal text-gray-500">単位：万円/日</span>
      </div>
      <ResponsiveContainer width="100%" height={260}>
        <LineChart data={rows} margin={{ top: 16, right: 16, left: 0, bottom: 8 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEF2F7" />
          <XAxis dataKey="month" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
          <YAxis tickLine={false} axisLine={false} tickFormatter={(v) => fmt1(v)} />
          <Tooltip formatter={(v) => [`${fmt1(v)} 万円/日`, '1日合計売上']} contentStyle={{ borderRadius: 8, border: '1px solid #E5E7EB', fontSize: 12 }} />
          <Line dataKey="totalPerDay" name="1日合計売上" stroke={COLORS.green} strokeWidth={3} type="monotone" dot={{ r: 4, fill: COLORS.green }} activeDot={{ r: 6 }}>
            <LabelList dataKey="totalPerDay" position="top" formatter={(v) => fmt1(v)} style={{ fontSize: 10, fill: COLORS.text }} />
          </Line>
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}
function ChartPerDay({ rows }) {
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title"><span className="badge">4</span>1日あたり 4項目別売上
        <span className="ml-auto text-[11px] font-normal text-gray-500">単位：万円/日</span>
      </div>
      <ResponsiveContainer width="100%" height={260}>
        <LineChart data={rows} margin={{ top: 16, right: 16, left: 0, bottom: 8 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEF2F7" />
          <XAxis dataKey="month" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
          <YAxis tickLine={false} axisLine={false} tickFormatter={(v) => fmt1(v)} />
          <Tooltip formatter={(v, name) => [`${fmt1(v)} 万円/日`, name]} contentStyle={{ borderRadius: 8, border: '1px solid #E5E7EB', fontSize: 12 }} />
          <Legend wrapperStyle={{ paddingTop: 4 }} />
          <Line dataKey="drugPerDay"         name="薬剤料/日"     stroke={FEE_COLORS.drugFee}         strokeWidth={2.5} type="monotone" dot={{ r: 3, fill: FEE_COLORS.drugFee }}         activeDot={{ r: 5 }} />
          <Line dataKey="pharmMgmtPerDay"    name="薬学管理料/日" stroke={FEE_COLORS.pharmMgmtFee}    strokeWidth={2.5} type="monotone" dot={{ r: 3, fill: FEE_COLORS.pharmMgmtFee }}    activeDot={{ r: 5 }} />
          <Line dataKey="dispenseTechPerDay" name="調剤技術料/日" stroke={FEE_COLORS.dispenseTechFee} strokeWidth={2.5} type="monotone" dot={{ r: 3, fill: FEE_COLORS.dispenseTechFee }} activeDot={{ r: 5 }} />
          <Line dataKey="medMaterialPerDay"  name="医療材料料/日" stroke={FEE_COLORS.medMaterialFee}  strokeWidth={2.5} type="monotone" dot={{ r: 3, fill: FEE_COLORS.medMaterialFee }}  activeDot={{ r: 5 }} />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}
/* 在庫金額（棒）の単独チャート */
function ChartInventory({ rows }) {
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title">
        <span className="badge" style={{ background: '#7C3AED' }}>6</span>
        在庫金額 推移
        <span className="ml-auto text-[11px] font-normal text-gray-500">単位：万円</span>
      </div>
      <ResponsiveContainer width="100%" height={300}>
        <ComposedChart data={rows} margin={{ top: 20, right: 24, left: 0, bottom: 8 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEF2F7" />
          <XAxis dataKey="month" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
          <YAxis tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)}  label={{ value: '万円', angle: -90, position: 'insideLeft', fill: '#94A3B8', fontSize: 11 }} />
          <Tooltip
            formatter={(v, name) => [`${fmt(v)} 万円`, name]}
            contentStyle={{ borderRadius: 8, border: '1px solid #E5E7EB', fontSize: 12 }}
          />
          <Legend wrapperStyle={{ paddingTop: 8 }} />
          <Bar dataKey="inventoryAmount" name="在庫金額" fill="#7C3AED" radius={[4, 4, 0, 0]} barSize={28} />
        </ComposedChart>
      </ResponsiveContainer>
      <div className="mt-2 text-[11px] text-gray-500">
        <i className="fas fa-circle-info mr-1"></i>月末時点の在庫金額（万円）の推移を表示しています。
      </div>
    </div>
  );
}
function GrowthCard({ label, ratio, sublabel }) {
  const isUp = ratio >= 1;
  const cls = !isFinite(ratio) || ratio === 0 ? '' : (isUp ? 'kpi-up' : 'kpi-down');
  const pct = (ratio - 1) * 100;
  const arrow = isUp ? '▲' : '▼';
  return (
    <div className={`kpi-card ${cls}`}>
      <div className="kpi-label">{label}</div>
      <div className="kpi-value font-num">×{fmt(ratio, 2)}</div>
      <div className="kpi-sub font-num">{arrow} {pct >= 0 ? '+' : ''}{fmt(pct, 1)}%</div>
      {sublabel && <div className="text-[11px] text-gray-500 mt-1">{sublabel}</div>}
    </div>
  );
}
/* 在庫専用カード（在庫は「増えれば良い」訳ではないので独自表現） */
function InventoryCard({ label, ratio, sublabel, isInventory = true }) {
  // 在庫成長率は「上昇＝積み増し（要注意）」「下降＝圧縮（良）」と解釈
  const isUp = ratio >= 1;
  const cls = !isFinite(ratio) || ratio === 0 ? '' : (isInventory ? (isUp ? 'kpi-down' : 'kpi-up') : (isUp ? 'kpi-up' : 'kpi-down'));
  const pct = (ratio - 1) * 100;
  const arrow = isUp ? '▲' : '▼';
  return (
    <div className={`kpi-card ${cls}`}>
      <div className="kpi-label">{label}</div>
      <div className="kpi-value font-num">×{fmt(ratio, 2)}</div>
      <div className="kpi-sub font-num">{arrow} {pct >= 0 ? '+' : ''}{fmt(pct, 1)}%</div>
      {sublabel && <div className="text-[11px] text-gray-500 mt-1">{sublabel}</div>}
    </div>
  );
}
function GrowthCards({ growth, rows }) {
  const first = rows[0], last = rows[rows.length - 1];
  const sub = first && last ? `${first.month} → ${last.month}` : '';
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title">
        <span className="badge">5</span>成長率 KPI <span className="text-gray-500 text-[11px] font-normal ml-1">（最終月 ÷ 初月）</span>
        <span className="ml-auto text-[11px] font-normal text-gray-500">{sub}</span>
      </div>
      <div className="grid grid-cols-2 gap-3">
        <GrowthCard label="薬剤料 成長率"     ratio={growth.drugGrowth}         sublabel={`${fmt(first?.drugFee)} → ${fmt(last?.drugFee)} 万円`} />
        <GrowthCard label="薬学管理料 成長率" ratio={growth.pharmMgmtGrowth}    sublabel={`${fmt(first?.pharmMgmtFee)} → ${fmt(last?.pharmMgmtFee)} 万円`} />
        <GrowthCard label="調剤技術料 成長率" ratio={growth.dispenseTechGrowth} sublabel={`${fmt(first?.dispenseTechFee)} → ${fmt(last?.dispenseTechFee)} 万円`} />
        <GrowthCard label="医療材料料 成長率" ratio={growth.medMaterialGrowth}  sublabel={`${fmt(first?.medMaterialFee)} → ${fmt(last?.medMaterialFee)} 万円`} />
        <GrowthCard label="総売上 成長率"     ratio={growth.totalGrowth}        sublabel={`${fmt(first?.total)} → ${fmt(last?.total)} 万円`} />
        <GrowthCard label="1日売上 成長率"    ratio={growth.perDayGrowth}       sublabel={`${fmt1(first?.totalPerDay)} → ${fmt1(last?.totalPerDay)} 万円/日`} />
        <InventoryCard label="在庫金額 推移"  ratio={growth.inventoryGrowth}    sublabel={`${fmt(first?.inventoryAmount)} → ${fmt(last?.inventoryAmount)} 万円`} />
      </div>
    </div>
  );
}

/* =====================================================================
   AI 総評パネル（最下段）
   - 既定はルールベース総評を表示
   - 「AIで生成」を押すと /api/insight (OpenAI) のSSEで本格的総評を逐次表示
   - 失敗時はルールベースにフォールバック表示のまま
   ===================================================================== */
function AIInsightPanel({ insight, title, aiPayload, aiMode = 'single' }) {
  const moodColor =
    insight.mood === 'good' ? COLORS.green :
    insight.mood === 'warn' ? COLORS.orange :
    insight.mood === 'bad'  ? COLORS.red :
                              COLORS.blue;

  const ai = useAIInsight();
  const isBusy = ai.status === 'loading' || ai.status === 'streaming';

  const handleGenerate = () => {
    if (!aiPayload) return;
    ai.run({ mode: aiMode, payload: aiPayload, target: title });
  };

  return (
    <div className="dash-card ai-insight" style={{ borderTop: `4px solid ${moodColor}` }}>
      <div className="dash-card-title">
        <span className="badge" style={{ background: moodColor }}>
          <i className="fas fa-wand-magic-sparkles text-[11px]"></i>
        </span>
        AI 総評 <span className="text-gray-500 text-[11px] font-normal ml-1">（{title}）</span>

        <div className="ml-auto flex items-center gap-2 no-print">
          {ai.status === 'done' && (
            <span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-green-50 text-green-700 border border-green-200">
              <i className="fas fa-check mr-1"></i>OpenAI 生成済
            </span>
          )}
          {ai.status === 'error' && (
            <span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-red-50 text-red-700 border border-red-200" title={ai.error || ''}>
              <i className="fas fa-triangle-exclamation mr-1"></i>失敗（フォールバック表示）
            </span>
          )}

          {!isBusy ? (
            <button
              onClick={handleGenerate}
              disabled={!aiPayload}
              className="text-[11px] font-bold px-3 py-1.5 rounded-md text-white shadow-sm disabled:opacity-40"
              style={{ background: 'linear-gradient(135deg,#7C3AED 0%,#1E5AAA 100%)' }}
              title="OpenAIで本格的な経営総評を生成します"
            >
              <i className="fas fa-wand-magic-sparkles mr-1"></i>
              {ai.status === 'done' || ai.text ? 'AIで再生成' : 'AIで生成'}
            </button>
          ) : (
            <button
              onClick={ai.stop}
              className="text-[11px] font-bold px-3 py-1.5 rounded-md text-white bg-gray-500 hover:bg-gray-600 shadow-sm"
            >
              <i className="fas fa-stop mr-1"></i>停止
            </button>
          )}

          {(ai.text || ai.status === 'error') && !isBusy && (
            <button
              onClick={ai.reset}
              className="text-[11px] font-bold px-2.5 py-1.5 rounded-md text-gray-600 bg-gray-100 hover:bg-gray-200"
              title="AI生成結果をクリアしてルールベース表示に戻す"
            >
              <i className="fas fa-rotate-left"></i>
            </button>
          )}

          <span className="text-[10px] font-normal px-2 py-0.5 rounded-full bg-gradient-to-r from-purple-50 to-blue-50 text-purple-600 border border-purple-100">
            <i className="fas fa-robot mr-1"></i>{ai.text ? 'OpenAI' : 'Auto'}
          </span>
        </div>
      </div>

      {/* AI生成結果（あれば優先表示）*/}
      {ai.text ? (
        <div className="ai-llm-body">
          <div className="ai-llm-meta">
            <i className="fas fa-microchip mr-1.5 text-purple-500"></i>
            OpenAI 生成 — {ai.status === 'streaming' ? '生成中…' : '完了'}
            {isBusy && <span className="ai-cursor">▍</span>}
          </div>
          <div className="ai-md">
            {renderMarkdown(ai.text)}
            {isBusy && <span className="ai-cursor inline-block">▍</span>}
          </div>
        </div>
      ) : (
        <>
          {/* ローディング中の擬似プログレス */}
          {isBusy && (
            <div className="ai-llm-loading">
              <div className="ai-llm-bar"><span></span></div>
              <div className="text-[12px] text-gray-500 mt-2">
                <i className="fas fa-circle-notch fa-spin mr-1.5 text-purple-500"></i>
                OpenAI が経営総評を生成しています…
              </div>
            </div>
          )}

          {/* ルールベースのフォールバック表示 */}
          <div className="ai-headline" style={{ color: moodColor }}>
            <i className="fas fa-quote-left text-[16px] mr-2 opacity-50"></i>
            {insight.headline}
          </div>

          {insight.tags && insight.tags.length > 0 && (
            <div className="flex flex-wrap gap-2 my-3">
              {insight.tags.map((t, i) => (
                <span key={i} className="ai-tag" style={{ background: `${t.color}15`, color: t.color, borderColor: `${t.color}40` }}>
                  <i className="fas fa-circle text-[7px] mr-1.5"></i>{t.label}
                </span>
              ))}
            </div>
          )}

          <div className="ai-body space-y-3">
            {insight.paragraphs.map((p, i) => (
              <p key={i} className="text-[13px] leading-relaxed text-brand-text">
                <span className="ai-num">{i + 1}</span>{p}
              </p>
            ))}
          </div>
        </>
      )}

      <div className="mt-4 pt-3 border-t border-brand-border text-[10px] text-gray-400 flex items-center gap-2">
        <i className="fas fa-info-circle"></i>
        {ai.text
          ? 'OpenAIが入力データから生成した総評です。数値の妥当性は実態と照らしてご確認ください。'
          : '現在はデータ駆動の自動生成（ルールベース）です。「AIで生成」でOpenAIによる本格総評に切り替えできます。'}
      </div>
    </div>
  );
}

/* =====================================================================
   ダッシュボード本体（単独モード）
   ===================================================================== */
const Dashboard = React.forwardRef(function Dashboard({ tab, rows, growth, subtitle, insight }, ref) {
  const period = rows.length > 0 ? `${rows[0].month} 〜 ${rows[rows.length - 1].month}` : '-';
  const totalSum = rows.reduce((a, b) => a + b.total, 0);
  const totalDays = rows.reduce((a, b) => a + (b.workingDays || 0), 0);
  const avgPerDay = totalDays > 0 ? totalSum / totalDays : 0;
  const lastInv  = rows.length > 0 ? (rows[rows.length - 1].inventoryAmount || 0) : 0;
  const badgeColor = tab.kind === 'store' ? COLORS.blue : tab.kind === 'company' ? COLORS.orange : COLORS.green;
  const badgeText  = tab.kind === 'store' ? '店舗' : tab.kind === 'company' ? '法人合計' : '全社合計';

  return (
    <div ref={ref} className="dashboard-area bg-white p-8 rounded-xl border border-brand-border">
      <div className="flex items-end justify-between gap-6 mb-6 pb-5 border-b border-brand-border flex-wrap">
        <div className="flex items-center gap-4">
          <img src="/static/logo.png" alt="IPB" className="h-14 w-auto" />
          <div>
            <div className="flex items-center gap-2">
              <span className="text-[11px] font-bold text-brand-blue tracking-widest">IWAKI PHARMA BOARD</span>
              <span className="text-[10px] font-bold px-2 py-0.5 rounded text-white" style={{ background: badgeColor }}>{badgeText}</span>
            </div>
            <div className="text-[26px] md:text-[30px] font-black text-brand-text leading-tight">{tab.fullName}</div>
            <div className="text-xs text-gray-500 mt-1">
              {subtitle && <span className="mr-3">{subtitle}</span>}
              対象期間：<span className="font-semibold text-brand-text">{period}</span> ／
              レポート出力日：{new Date().toLocaleDateString('ja-JP')}
            </div>
          </div>
        </div>
        <div className="hidden md:grid grid-cols-4 gap-4 text-right">
          <div>
            <div className="text-[11px] text-gray-500">期間合計売上</div>
            <div className="text-xl font-bold font-num text-brand-green">{fmt(totalSum)}<span className="text-xs ml-1 text-gray-500">万円</span></div>
          </div>
          <div>
            <div className="text-[11px] text-gray-500">期間合計営業日</div>
            <div className="text-xl font-bold font-num text-brand-text">{fmt(totalDays)}<span className="text-xs ml-1 text-gray-500">日</span></div>
          </div>
          <div>
            <div className="text-[11px] text-gray-500">期間平均 1日売上</div>
            <div className="text-xl font-bold font-num text-brand-blue">{fmt1(avgPerDay)}<span className="text-xs ml-1 text-gray-500">万円/日</span></div>
          </div>
          <div>
            <div className="text-[11px] text-gray-500">直近月 在庫金額</div>
            <div className="text-xl font-bold font-num" style={{ color: '#7C3AED' }}>
              {fmt(lastInv)}<span className="text-xs ml-1 text-gray-500">万円</span>
            </div>
          </div>
        </div>
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
        <ChartCombo rows={rows} />
        <DataTable rows={rows} />
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
        <ChartTotalDay rows={rows} />
        <ChartPerDay rows={rows} />
        <GrowthCards growth={growth} rows={rows} />
      </div>

      {/* 在庫専用エリア */}
      <div className="grid grid-cols-1 mb-6">
        <ChartInventory rows={rows} />
      </div>

      {/* AI総評パネル */}
      <AIInsightPanel
        insight={insight}
        title={tab.fullName}
        aiMode="single"
        aiPayload={buildInsightPayload(rows, growth, tab.fullName)}
      />

      <div className="mt-6 pt-4 border-t border-brand-border flex items-center justify-between text-[11px] text-gray-500 flex-wrap gap-2">
        <div><i className="fas fa-circle-info mr-1"></i>すべての金額は「万円」表記。1日あたり = 各項目 / 営業日数。成長率 = 最終月 / 初月。</div>
        <div className="font-semibold text-brand-blue">© Iwaki Pharma Board (IPB)</div>
      </div>
    </div>
  );
});

/* =====================================================================
   比較チャート群
   ===================================================================== */
/* 月軸を統一して、シリーズごとに値を持つ wide-format に変換 */
function buildCompareData(seriesList, valueKey) {
  const monthSet = new Set();
  seriesList.forEach(s => s.rows.forEach(r => monthSet.add(r.month)));
  const months = Array.from(monthSet).sort();
  return months.map(m => {
    const obj = { month: m };
    seriesList.forEach(s => {
      const row = s.rows.find(r => r.month === m);
      obj[s.key] = row ? row[valueKey] : null;
    });
    return obj;
  });
}

function CompareChart({ seriesList, valueKey, title, unit, badge }) {
  const data = buildCompareData(seriesList, valueKey);
  const unitText = unit.replace('単位：', '');
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title">
        <span className="badge" style={{ background: valueKey.startsWith('inventory') ? '#7C3AED' : undefined }}>{badge}</span>{title}
        <span className="ml-auto text-[11px] font-normal text-gray-500">{unit}</span>
      </div>
      <ResponsiveContainer width="100%" height={300}>
        <LineChart data={data} margin={{ top: 20, right: 16, left: 0, bottom: 8 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEF2F7" />
          <XAxis dataKey="month" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
          <YAxis tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} domain={['auto', 'auto']} />
          <Tooltip formatter={(v, name) => [v == null ? '-' : `${fmt(v)} ${unitText}`, name]} contentStyle={{ borderRadius: 8, border: '1px solid #E5E7EB', fontSize: 12 }} />
          <Legend wrapperStyle={{ paddingTop: 8, fontSize: 12 }} />
          {seriesList.map((s, i) => (
            <Line key={s.key} dataKey={s.key} name={s.name}
              stroke={SERIES_COLORS[i % SERIES_COLORS.length]} strokeWidth={2.5} type="monotone"
              dot={{ r: 3 }} activeDot={{ r: 5 }} connectNulls={true} />
          ))}
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}

/* 比較サマリーテーブル */
function CompareSummaryTable({ seriesList }) {
  const rows = seriesList.map((s, i) => {
    const last = s.rows[s.rows.length - 1] || {};
    const first = s.rows[0] || {};
    const totalSum = s.rows.reduce((a, b) => a + b.total, 0);
    const totalDays = s.rows.reduce((a, b) => a + (b.workingDays || 0), 0);
    const avgPerDay = totalDays > 0 ? totalSum / totalDays : 0;
    const totalPct = (s.growth.totalGrowth - 1) * 100;
    const perDayPct = (s.growth.perDayGrowth - 1) * 100;
    return {
      key: s.key, name: s.name, color: SERIES_COLORS[i % SERIES_COLORS.length],
      lastMonth: last.month, lastTotal: last.total || 0, lastPerDay: last.totalPerDay || 0,
      totalSum, avgPerDay, totalPct, perDayPct, count: s.rows.length,
      first: first.month, last: last.month,
      lastInv: last.inventoryAmount || 0,
    };
  });
  return (
    <div className="dash-card h-full">
      <div className="dash-card-title">
        <span className="badge"><i className="fas fa-table"></i></span>比較サマリー（最終月 / 期間合計 / 成長率 / 在庫）
      </div>
      <div className="overflow-x-auto scrollbar-thin">
        <table className="ipb-table">
          <thead><tr>
            <th>対象</th>
            <th className="text-right">期間</th>
            <th className="text-right">直近月 売上</th>
            <th className="text-right">直近月 1日売上</th>
            <th className="text-right">期間合計売上</th>
            <th className="text-right">期間平均 1日売上</th>
            <th className="text-right">総売上成長率</th>
            <th className="text-right">1日売上成長率</th>
            <th className="text-right">直近月 在庫</th>
          </tr></thead>
          <tbody>
            {rows.map(r => (
              <tr key={r.key}>
                <td className="font-semibold">
                  <span className="series-dot mr-2" style={{ background: r.color }}></span>
                  {r.name}
                </td>
                <td className="num">{r.first} 〜 {r.last}</td>
                <td className="num font-bold">{fmt(r.lastTotal)}</td>
                <td className="num">{fmt1(r.lastPerDay)}</td>
                <td className="num">{fmt(r.totalSum)}</td>
                <td className="num">{fmt1(r.avgPerDay)}</td>
                <td className="num font-bold" style={{ color: r.totalPct >= 0 ? COLORS.green : COLORS.red }}>
                  {r.totalPct >= 0 ? '+' : ''}{fmt(r.totalPct, 1)}%
                </td>
                <td className="num font-bold" style={{ color: r.perDayPct >= 0 ? COLORS.green : COLORS.red }}>
                  {r.perDayPct >= 0 ? '+' : ''}{fmt(r.perDayPct, 1)}%
                </td>
                <td className="num" style={{ color: '#7C3AED' }}>{fmt(r.lastInv)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="mt-2 text-[11px] text-gray-500 text-right">単位：万円（成長率は最終月÷初月）</div>
    </div>
  );
}

/* 比較ダッシュボード（複数選択モード） */
const CompareDashboard = React.forwardRef(function CompareDashboard({ seriesList, insight }, ref) {
  if (seriesList.length === 0) {
    return (
      <div ref={ref} className="dashboard-area bg-white p-8 rounded-xl border border-brand-border">
        <div className="text-center py-16 text-gray-500">
          <i className="fas fa-code-compare text-4xl text-gray-300 mb-3"></i>
          <div>比較したい店舗・法人を上のチェックから選択してください。</div>
        </div>
      </div>
    );
  }
  // 期間レンジ
  const allMonths = seriesList.flatMap(s => s.rows.map(r => r.month));
  const minM = allMonths.length ? allMonths.reduce((a, b) => a < b ? a : b) : '-';
  const maxM = allMonths.length ? allMonths.reduce((a, b) => a > b ? a : b) : '-';

  return (
    <div ref={ref} className="dashboard-area bg-white p-8 rounded-xl border border-brand-border">
      <div className="flex items-end justify-between gap-6 mb-6 pb-5 border-b border-brand-border flex-wrap">
        <div className="flex items-center gap-4">
          <img src="/static/logo.png" alt="IPB" className="h-14 w-auto" />
          <div>
            <div className="flex items-center gap-2">
              <span className="text-[11px] font-bold text-brand-blue tracking-widest">IWAKI PHARMA BOARD</span>
              <span className="text-[10px] font-bold px-2 py-0.5 rounded text-white" style={{ background: '#7C3AED' }}>比較モード</span>
            </div>
            <div className="text-[26px] md:text-[30px] font-black text-brand-text leading-tight">
              比較ダッシュボード <span className="text-gray-500 text-base font-normal">（{seriesList.length}件）</span>
            </div>
            <div className="text-xs text-gray-500 mt-1">
              対象：<span className="font-semibold text-brand-text">{seriesList.map(s => s.name).join(' / ')}</span>
            </div>
            <div className="text-xs text-gray-500">
              期間：<span className="font-semibold text-brand-text">{minM} 〜 {maxM}</span> ／
              レポート出力日：{new Date().toLocaleDateString('ja-JP')}
            </div>
          </div>
        </div>
      </div>

      {/* 比較チャート */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
        <CompareChart seriesList={seriesList} valueKey="total"       title="月別 合計売上 比較"          unit="単位：万円"     badge="A" />
        <CompareChart seriesList={seriesList} valueKey="totalPerDay" title="月別 1日あたり合計売上 比較" unit="単位：万円/日"  badge="B" />
      </div>
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
        <CompareChart seriesList={seriesList} valueKey="drugFee"         title="月別 薬剤料 比較"     unit="単位：万円" badge="C" />
        <CompareChart seriesList={seriesList} valueKey="pharmMgmtFee"    title="月別 薬学管理料 比較" unit="単位：万円" badge="D" />
      </div>
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
        <CompareChart seriesList={seriesList} valueKey="dispenseTechFee" title="月別 調剤技術料 比較" unit="単位：万円" badge="E" />
        <CompareChart seriesList={seriesList} valueKey="medMaterialFee"  title="月別 医療材料料 比較" unit="単位：万円" badge="F" />
      </div>
      <div className="grid grid-cols-1 mb-6">
        <CompareChart seriesList={seriesList} valueKey="inventoryAmount" title="月別 在庫金額 比較"   unit="単位：万円" badge="G" />
      </div>

      {/* サマリーテーブル */}
      <div className="mb-6">
        <CompareSummaryTable seriesList={seriesList} />
      </div>

      {/* AI総評（比較版） */}
      <AIInsightPanel
        insight={insight}
        title={`${seriesList.length}件比較（${seriesList.map(s => s.name).join(' / ')}）`}
        aiMode="compare"
        aiPayload={buildComparePayload(seriesList)}
      />

      <div className="mt-6 pt-4 border-t border-brand-border flex items-center justify-between text-[11px] text-gray-500 flex-wrap gap-2">
        <div><i className="fas fa-circle-info mr-1"></i>欠損月は線をつないで描画します。タブをまたいだ集計は法人/全社タブの自動集計を参照しています。</div>
        <div className="font-semibold text-brand-blue">© Iwaki Pharma Board (IPB)</div>
      </div>
    </div>
  );
});

/* =====================================================================
   App
   ===================================================================== */
function App() {
  const [stores, setStores]       = useState(() => makeAllSamples());
  const [activeTab, setActiveTab] = useState(STORES[0].id);
  const [mode, setMode]           = useState('single'); // 'single' | 'compare'
  const [view, setView]           = useState('dashboard'); // 'dashboard' | 'input'
  const [selectedIds, setSelectedIds] = useState([STORES[0].id, STORES[3].id]); // 初期：南ヶ丘 vs 兼六
  const [isExporting, setIsExporting] = useState(false);
  const [toastNode, showToast]    = useToast();
  const dashRef = useRef(null);
  const compareRef = useRef(null);

  /* localStorage 復元 */
  useEffect(() => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (raw) {
        const obj = JSON.parse(raw);
        if (obj && obj.stores && typeof obj.stores === 'object') {
          const restored = {};
          STORES.forEach(s => {
            restored[s.id] = Array.isArray(obj.stores[s.id]) ? obj.stores[s.id] : [];
          });
          const allEmpty = STORES.every(s => (restored[s.id] || []).length === 0);
          setStores(allEmpty ? makeAllSamples() : restored);
        }
        if (typeof obj.activeTab === 'string' && TABS.find(t => t.id === obj.activeTab)) {
          setActiveTab(obj.activeTab);
        }
        if (Array.isArray(obj.selectedIds)) {
          setSelectedIds(obj.selectedIds.filter(id => TABS.find(t => t.id === id)));
        }
        if (obj.mode === 'single' || obj.mode === 'compare') setMode(obj.mode);
      }
    } catch (e) { /* ignore */ }
  }, []);

  /* localStorage 保存 */
  useEffect(() => {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({ stores, activeTab, selectedIds, mode }));
    } catch (e) { /* ignore */ }
  }, [stores, activeTab, selectedIds, mode]);

  /* 単独モード用 */
  const currentTab = useMemo(() => TABS.find(t => t.id === activeTab) || TABS[0], [activeTab]);
  const { displayRaw, sourceStores } = useMemo(() => {
    if (currentTab.kind === 'store') {
      const arr = stores[currentTab.id] || [];
      return { displayRaw: [...arr].sort((a, b) => String(a.month).localeCompare(String(b.month))), sourceStores: [STORES.find(s => s.id === currentTab.id)] };
    }
    if (currentTab.kind === 'company') {
      return { displayRaw: aggregateStores(stores, currentTab.storeIds), sourceStores: STORES.filter(s => currentTab.storeIds.includes(s.id)) };
    }
    return { displayRaw: aggregateStores(stores, STORES.map(s => s.id)), sourceStores: STORES };
  }, [currentTab, stores]);
  const rows   = useMemo(() => calcRows(displayRaw), [displayRaw]);
  const growth = useMemo(() => calcGrowth(rows), [rows]);
  const insight = useMemo(() => generateInsight(rows, growth, currentTab), [rows, growth, currentTab]);

  /* 比較モード用 */
  const compareSeries = useMemo(() => {
    return selectedIds.map(id => {
      const tab = TABS.find(t => t.id === id);
      const r = getRowsForTab(id, stores);
      return { key: id, name: tab?.label || id, fullName: tab?.fullName || id, kind: tab?.kind, rows: r, growth: calcGrowth(r) };
    }).filter(s => s.rows.length > 0);
  }, [selectedIds, stores]);
  const compareInsight = useMemo(() => generateCompareInsight(compareSeries), [compareSeries]);

  /* 行操作 */
  const updateStoreRows = (storeId, updater) => setStores(prev => ({ ...prev, [storeId]: updater(prev[storeId] || []) }));
  const handleAddRow = () => {
    if (currentTab.kind !== 'store') return;
    updateStoreRows(currentTab.id, (arr) => {
      const last = arr[arr.length - 1]; let nextMonth = '2026-01';
      if (last?.month) {
        const [y, m] = last.month.split('-').map(Number);
        const d = new Date(y, m, 1);
        nextMonth = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
      }
      return [...arr, { month: nextMonth, workingDays: 24, drugFee: 0, pharmMgmtFee: 0, dispenseTechFee: 0, medMaterialFee: 0, inventoryAmount: 0 }];
    });
  };
  const handleRemoveRow = (i) => { if (currentTab.kind !== 'store') return; updateStoreRows(currentTab.id, (arr) => arr.filter((_, idx) => idx !== i)); };
  const handleUpdateRow = (i, key, value) => { if (currentTab.kind !== 'store') return; updateStoreRows(currentTab.id, (arr) => arr.map((r, idx) => idx === i ? { ...r, [key]: value } : r)); };
  const handleClear = () => {
    if (currentTab.kind !== 'store') return;
    if (!confirm(`「${currentTab.fullName}」のすべての行を削除します。よろしいですか？`)) return;
    updateStoreRows(currentTab.id, () => []); showToast('この店舗のデータを削除しました');
  };
  const handleLoadSample = () => {
    if (currentTab.kind !== 'store') return;
    if ((stores[currentTab.id] || []).length > 0 && !confirm(`「${currentTab.fullName}」のデータをサンプルで上書きします。よろしいですか？`)) return;
    updateStoreRows(currentTab.id, () => makeSample(currentTab.id)); showToast('サンプルデータを読み込みました');
  };
  const handleImportCSV = (text) => {
    if (currentTab.kind !== 'store') return;
    try {
      const parsed = parseCSV(text);
      if (parsed.length === 0) throw new Error('データ行がありません');
      updateStoreRows(currentTab.id, () => parsed); showToast(`CSVから ${parsed.length} 件を読み込みました`);
    } catch (e) { alert('CSVの読み込みに失敗しました：\n' + e.message); }
  };
  const handleResetAllSamples = () => {
    if (!confirm('全店舗のデータをサンプルで上書きします。よろしいですか？')) return;
    setStores(makeAllSamples()); showToast('全店舗にサンプルデータを読み込みました');
  };

  /* エクスポート */
  const captureCanvas = async (target) => {
    if (!target) throw new Error('ダッシュボードが見つかりません');
    return await html2canvas(target, { scale: 2, backgroundColor: '#FFFFFF', useCORS: true, logging: false, windowWidth: target.scrollWidth });
  };
  const filenameBase = () => {
    const safe = mode === 'compare'
      ? `compare_${compareSeries.length}items`
      : (currentTab.label || 'pharmacy').replace(/[^\w\u3040-\u30ff\u4e00-\u9fff]/g, '_');
    return `IPB_${safe}_${todayStr()}`;
  };
  const handleExportPNG = async () => {
    setIsExporting(true);
    try {
      const target = mode === 'compare' ? compareRef.current : dashRef.current;
      const canvas = await captureCanvas(target);
      const a = document.createElement('a');
      a.href = canvas.toDataURL('image/png');
      a.download = `${filenameBase()}.png`;
      a.click(); showToast('PNGをダウンロードしました');
    } catch (e) { alert('PNG出力に失敗しました：' + e.message); }
    finally { setIsExporting(false); }
  };
  const handleExportPDF = async () => {
    setIsExporting(true);
    try {
      const target = mode === 'compare' ? compareRef.current : dashRef.current;
      const canvas = await captureCanvas(target);
      const imgData = canvas.toDataURL('image/png');
      const { jsPDF } = window.jspdf;
      const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a3' });
      const pageW = pdf.internal.pageSize.getWidth(); const pageH = pdf.internal.pageSize.getHeight();
      const margin = 8; const maxW = pageW - margin * 2; const maxH = pageH - margin * 2;
      const ratio = canvas.width / canvas.height;
      let w = maxW; let h = w / ratio;
      if (h > maxH) { h = maxH; w = h * ratio; }
      pdf.addImage(imgData, 'PNG', (pageW - w) / 2, (pageH - h) / 2, w, h, undefined, 'FAST');
      pdf.save(`${filenameBase()}.pdf`); showToast('PDFをダウンロードしました');
    } catch (e) { alert('PDF出力に失敗しました：' + e.message); }
    finally { setIsExporting(false); }
  };

  const subtitle = currentTab.kind === 'store' ? '店舗別ダッシュボード'
                 : currentTab.kind === 'company' ? `${sourceStores.map(s => s.short).join(' + ')} の合算`
                 : `${STORES.length}店舗 全社合算`;

  return (
    <div className="min-h-screen">
      {isExporting && <div className="loading-overlay"><i className="fas fa-spinner fa-spin mr-2"></i>出力処理中...</div>}
      {toastNode}

      <Header mode={mode} setMode={setMode} view={view} setView={setView} />

      {mode === 'single'
        ? <TabBar activeTab={activeTab} setActiveTab={setActiveTab} />
        : <CompareSelector selectedIds={selectedIds} setSelectedIds={setSelectedIds} />}

      <main className="max-w-[1920px] mx-auto px-6 md:px-10 py-6 space-y-6">
        {/* === データ入力ページ === */}
        {mode === 'single' && view === 'input' && (
          <>
            <div className="dash-card flex items-center gap-3 flex-wrap">
              <div className="flex items-center gap-2">
                <i className="fas fa-keyboard text-brand-blue"></i>
                <span className="text-sm font-bold text-brand-text">データ入力モード</span>
                <span className="text-[11px] text-gray-500">（{currentTab.fullName}）</span>
              </div>
              <div className="flex-1"></div>
              <button className="btn btn-primary" onClick={() => setView('dashboard')}>
                <i className="fas fa-chart-line"></i>ダッシュボードを表示
              </button>
            </div>

            {currentTab.kind === 'store'
              ? <InputPanel data={stores[currentTab.id] || []}
                  onAddRow={handleAddRow} onRemoveRow={handleRemoveRow} onUpdateRow={handleUpdateRow}
                  onImportCSV={handleImportCSV} onLoadSample={handleLoadSample} onClear={handleClear} />
              : <AggregatePanel tab={currentTab} rows={displayRaw} sourceStores={sourceStores} />}

            <div className="no-print flex flex-wrap items-center gap-2">
              <div className="text-[11px] text-gray-500 mr-2">
                <i className="fas fa-database mr-1"></i>
                データは店舗ごとに分離して保存されます。タブを切り替えても消えません。
              </div>
              <div className="flex-1"></div>
              <button className="btn btn-secondary" onClick={handleResetAllSamples}>
                <i className="fas fa-flask"></i>全店サンプル読込
              </button>
              <button className="btn btn-primary" onClick={() => setView('dashboard')}>
                <i className="fas fa-arrow-right"></i>入力完了 → ダッシュボードへ
              </button>
            </div>
          </>
        )}

        {/* === ダッシュボードページ === */}
        {(view === 'dashboard' || mode === 'compare') && (
          <>
            {/* 比較モード時は集計テーブルを軽く表示 */}
            {mode === 'single' && currentTab.kind !== 'store' && (
              <AggregatePanel tab={currentTab} rows={displayRaw} sourceStores={sourceStores} />
            )}

            {/* アクションバー */}
            <div className="no-print flex flex-wrap items-center gap-2">
              <div className="text-[11px] text-gray-500 mr-2">
                <i className="fas fa-database mr-1"></i>
                {mode === 'single'
                  ? 'ダッシュボード表示中。データ修正は右上「データ入力」ボタンから。'
                  : `${selectedIds.length}件を比較中。チェックで対象を増減できます。`}
              </div>
              <div className="flex-1"></div>
              {mode === 'single' && currentTab.kind === 'store' && (
                <button className="btn btn-secondary" onClick={() => setView('input')}>
                  <i className="fas fa-keyboard"></i>データ入力
                </button>
              )}
              <button className="btn btn-success" disabled={isExporting} onClick={handleExportPNG}>
                <i className="fas fa-image"></i>PNGダウンロード
              </button>
              <button className="btn btn-primary" disabled={isExporting} onClick={handleExportPDF}>
                <i className="fas fa-file-pdf"></i>PDFダウンロード
              </button>
            </div>

            {/* メインダッシュボード */}
            {mode === 'single' ? (
              rows.length === 0 ? (
                <div className="dash-card text-center py-16 text-gray-500">
                  <i className="fas fa-chart-line text-4xl text-gray-300 mb-3"></i>
                  <div className="mb-4">データがありません。
                    {currentTab.kind === 'store' ? '「データ入力」ページから入力するか、サンプルを読み込んでください。' : '対象店舗のデータを各店舗タブで入力してください。'}
                  </div>
                  {currentTab.kind === 'store' && (
                    <button className="btn btn-primary" onClick={() => setView('input')}>
                      <i className="fas fa-keyboard"></i>データ入力ページへ
                    </button>
                  )}
                </div>
              ) : (
                <Dashboard ref={dashRef} tab={currentTab} rows={rows} growth={growth} subtitle={subtitle} insight={insight} />
              )
            ) : (
              <CompareDashboard ref={compareRef} seriesList={compareSeries} insight={compareInsight} />
            )}
          </>
        )}

        <footer className="no-print py-6 text-center text-xs text-gray-400">
          Iwaki Pharma Board (IPB) — Pharmacy Insight Dashboard / © {new Date().getFullYear()}
        </footer>
      </main>
    </div>
  );
}

/* Mount */
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
