// ===== Customers =====
// 広告チャネル定義（デフォルト）。木村さんが店舗設定→集客チャネル管理で追加・編集・削除できる。
// isPaid=true → 月額広告費を入力してCPAを算出
// isPaid=false → 人数のみ集計（CPA計算なし）
const DEFAULT_AD_CHANNELS = [
  // 費用発生チャネル
  { key: 'google',     label: 'Google広告',     isPaid: true },
  { key: 'newspaper',  label: '新聞折込',        isPaid: true },
  { key: 'kairan',     label: '回覧板',          isPaid: true },
  { key: 'instagram',  label: 'Instagram広告',  isPaid: true },
  { key: 'seo',        label: 'SEO',           isPaid: true },
  { key: 'liberty',    label: 'リベシティ',      isPaid: true },
  // ゼロ円チャネル
  { key: 'gbp',        label: 'GBP / Google検索', isPaid: false },
  { key: 'referral',   label: '紹介',           isPaid: false },
  { key: 'signboard',  label: '看板',           isPaid: false },
  { key: 'walkin',     label: '通りすがり',      isPaid: false },
  { key: 'other',      label: 'その他',         isPaid: false },
];
window.DEFAULT_AD_CHANNELS = DEFAULT_AD_CHANNELS;
function getActiveAdChannels(settings) {
  if (settings && Array.isArray(settings.adChannels) && settings.adChannels.length > 0) {
    return settings.adChannels;
  }
  return DEFAULT_AD_CHANNELS;
}
window.getActiveAdChannels = getActiveAdChannels;

// 顧客のリピートステージを判定
function customerStage(c) {
  const v = c.visits || 0;
  if (v === 0) return 'none';
  if (v === 1) return 'first';
  if (v === 2) return 'second';
  return 'regular';
}

// =====================================================
// 回数券・月額プラン ヘルパー
// =====================================================
const TICKET_DEFS = {
  ticket10: {
    label: '回数券 10回',
    totalCount: 10,
    pricePerVisit: 7200,
    totalPrice: 72000,
    durationDays: null, // 期限なし
  },
  monthly4: {
    label: '月額プラン（4回/月）',
    totalCount: 4,
    pricePerVisit: 7000,
    monthlyPrice: 28000,
    durationDays: 30, // 30日サイクル
  },
};

function generateTicketId() {
  return 'tkt_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7);
}

function createTicket10() {
  const today = new Date().toISOString().slice(0, 10);
  return {
    id: generateTicketId(),
    type: 'ticket10',
    purchasedAt: today,
    totalCount: 10,
    usedCount: 0,
    pricePerVisit: 7200,
    totalPrice: 72000,
    expiresAt: null,
    status: 'active',
  };
}

function createMonthly4() {
  const today = new Date();
  const todayISO = today.toISOString().slice(0, 10);
  const cycleEnd = new Date(today);
  cycleEnd.setDate(cycleEnd.getDate() + 30);
  return {
    id: generateTicketId(),
    type: 'monthly4',
    contractedAt: todayISO,
    cycleStart: todayISO,
    cycleEnd: cycleEnd.toISOString().slice(0, 10),
    cycleUsed: 0,
    monthlyTotal: 4,
    pricePerVisit: 7000,
    monthlyPrice: 28000,
    status: 'active',
  };
}

// 月額プラン：期限切れなら新サイクルへ
function rolloverMonthly(t, todayISO) {
  if (t.type !== 'monthly4') return t;
  if (todayISO <= t.cycleEnd) return t;
  // 期限切れ → 新サイクル
  const start = new Date(todayISO);
  const end = new Date(start);
  end.setDate(end.getDate() + 30);
  return {
    ...t,
    cycleStart: todayISO,
    cycleEnd: end.toISOString().slice(0, 10),
    cycleUsed: 0,
  };
}

// アクティブな（残数があり期限内の）チケットだけ
function getActiveTickets(customer, todayISO) {
  const today = todayISO || new Date().toISOString().slice(0, 10);
  const tickets = Array.isArray(customer.tickets) ? customer.tickets : [];
  return tickets.filter(t => {
    // status='active' or active=true、両方の表記を許容
    const isActive = t.status === 'active' || t.active === true ||
                     (t.status === undefined && t.active === undefined); // フィールド未設定でも許容
    if (!isActive) return false;
    if (t.type === 'ticket10') {
      if (t.usedCount >= t.totalCount) return false;
      if (t.expiresAt && t.expiresAt < today) return false;
      return true;
    }
    if (t.type === 'monthly4') {
      // 月額プランは契約中なら active（期限切れたらサイクル更新）
      return true;
    }
    return false;
  }).map(t => rolloverMonthly(t, today));
}

function ticketRemainingLabel(t, todayISO) {
  const today = todayISO || new Date().toISOString().slice(0, 10);
  if (t.type === 'ticket10') {
    return `残り ${t.totalCount - t.usedCount}/${t.totalCount} 回`;
  }
  if (t.type === 'monthly4') {
    const cur = rolloverMonthly(t, today);
    return `今サイクル ${cur.monthlyTotal - cur.cycleUsed}/${cur.monthlyTotal} 回（${cur.cycleEnd} まで）`;
  }
  return '';
}
const STAGE_LABEL = { none: '未来院', first: '初回のみ', second: '2回目', regular: '常連' };
const STAGE_STYLE = {
  none:    { background: '#eee',     color: '#777' },
  first:   { background: '#fdf0d6',  color: '#a87320' },
  second:  { background: '#e8f0e6',  color: '#4d7d3a' },
  regular: { background: '#d8e6cf',  color: '#1f3a14' },
};

function CustomersPage({ customers, bookings, settings, onSetCustomers, onAddBookingFor, addHandlerRef }) {
  const [q, setQ] = useState('');
  const [stageFilter, setStageFilter] = useState('all');
  const [retentionFilter, setRetentionFilter] = useState('all'); // active / warm / cool / lost / all
  const [sortKey, setSortKey] = useState('lastVisit'); // lastVisit / chartNo / visits / totalSpent / age / name
  const [sortDir, setSortDir] = useState('desc'); // asc / desc
  const [open, setOpen] = useState(null); // customer detail
  const [editing, setEditing] = useState(null); // { customer, isNew }
  const [dupModal, setDupModal] = useState(null); // [ {phoneDigits, customers}, ... ]

  // 予約モーダルなど他画面から「顧客カルテを開く」イベントを受信
  React.useEffect(() => {
    const handler = (e) => {
      const cid = e.detail && e.detail.customerId;
      if (!cid) return;
      const cust = (customers || []).find(c => c.id === cid);
      if (cust) {
        // 詳細モーダルを開いてさらに編集モードへ自動進行（領収書・支払い等を直接編集できる）
        setOpen(cust);
        setEditing({ customer: cust, isNew: false });
      }
    };
    window.addEventListener('physio:open-customer', handler);
    return () => window.removeEventListener('physio:open-customer', handler);
  }, [customers]);

  // リテンション分類（最終来院日からの経過月数で判定）
  const retentionOf = (c) => {
    if (!c.lastVisit) return 'none';
    const lv = new Date(c.lastVisit);
    const today = new Date();
    const months = (today.getFullYear() - lv.getFullYear()) * 12 + (today.getMonth() - lv.getMonth());
    const C = D.CONSTANTS;
    if (months < C.RETENTION_ACTIVE_MONTHS) return 'active';
    if (months < C.RETENTION_WARM_MONTHS) return 'warm';
    if (months < C.RETENTION_COOL_MONTHS) return 'cool';
    return 'lost';
  };

  const filtered = useMemo(() => {
    let arr = customers.slice();
    if (stageFilter !== 'all') {
      arr = arr.filter(c => customerStage(c) === stageFilter);
    }
    if (retentionFilter !== 'all') {
      arr = arr.filter(c => retentionOf(c) === retentionFilter);
    }
    if (q.trim()) {
      // ひらがな→カタカナ変換（検索を緩める）
      const hiraToKata = (s) => String(s || '').replace(/[ぁ-ゖ]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 0x60));
      // 全角/半角スペース・記号を除去して比較（"ナカムラ ミドリ" と "ナカムラミドリ" を一致）
      const stripSep = (s) => String(s || '').replace(/[\s　・\-_,.]+/g, '');
      // 数字以外を除く（電話番号比較用）
      const digits = (s) => String(s || '').replace(/[^0-9]/g, '');
      const kRaw = q.toLowerCase().trim();
      const k = stripSep(kRaw);
      const kKata = stripSep(hiraToKata(kRaw));
      const kDigits = digits(q);
      arr = arr.filter(c => {
        if (stripSep((c.name || '').toLowerCase()).includes(k)) return true;
        // kana列：ひらがな入力でもカタカナ登録にマッチ
        if (stripSep(hiraToKata((c.kana || '').toLowerCase())).includes(kKata)) return true;
        if ((c.email || '').toLowerCase().includes(kRaw)) return true;
        if (String(c.chartNo || '').toLowerCase().includes(kRaw)) return true;
        // 電話番号：数字部分だけで部分一致（下4桁検索もOK）
        if (kDigits.length >= 2) {
          const phone1 = digits(c.tel || c.phone);
          const phone2 = digits(c.phone2);
          if (phone1.includes(kDigits) || phone2.includes(kDigits)) return true;
        }
        return false;
      });
    }
    // ソート処理
    const dir = sortDir === 'asc' ? 1 : -1;
    arr.sort((a, b) => {
      let av, bv;
      switch (sortKey) {
        case 'chartNo': {
          // 数値として比較（A-001 形式は文字列比較）
          const aN = parseInt(a.chartNo, 10);
          const bN = parseInt(b.chartNo, 10);
          if (!isNaN(aN) && !isNaN(bN)) return (aN - bN) * dir;
          return (String(a.chartNo || '').localeCompare(String(b.chartNo || ''))) * dir;
        }
        case 'visits':     av = a.visits || 0;     bv = b.visits || 0;     return (av - bv) * dir;
        case 'totalSpent': av = a.totalSpent || 0; bv = b.totalSpent || 0; return (av - bv) * dir;
        case 'age':        av = a.age || 0;        bv = b.age || 0;        return (av - bv) * dir;
        case 'name':       return (String(a.kana || a.name || '').localeCompare(String(b.kana || b.name || ''))) * dir;
        case 'lastVisit':
        default:
          return (String(a.lastVisit || '').localeCompare(String(b.lastVisit || ''))) * dir;
      }
    });
    return arr;
  }, [customers, q, stageFilter, retentionFilter, sortKey, sortDir]);

  // ステージ別件数
  const stageCounts = useMemo(() => ({
    all: customers.length,
    first: customers.filter(c => customerStage(c) === 'first').length,
    second: customers.filter(c => customerStage(c) === 'second').length,
    regular: customers.filter(c => customerStage(c) === 'regular').length,
    none: customers.filter(c => customerStage(c) === 'none').length,
  }), [customers]);

  // リテンション別件数
  const retentionCounts = useMemo(() => ({
    active: customers.filter(c => retentionOf(c) === 'active').length,
    warm: customers.filter(c => retentionOf(c) === 'warm').length,
    cool: customers.filter(c => retentionOf(c) === 'cool').length,
    lost: customers.filter(c => retentionOf(c) === 'lost').length,
  }), [customers]);

  const totalRevenue = useMemo(() => customers.reduce((s, c) => s + (c.totalSpent || 0), 0), [customers]);
  const repeaters = customers.filter(c => (c.visits || 0) >= 2).length;
  const repeatRate = customers.length === 0 ? 0 : Math.round(repeaters / customers.length * 100);

  const handleSaveCustomer = (c) => {
    onSetCustomers(prev => {
      const exists = prev.some(x => x.id === c.id);
      return exists ? prev.map(x => x.id === c.id ? c : x) : [...prev, c];
    });
    setEditing(null);
    if (open && open.id === c.id) setOpen(c);
  };

  // 次のカルテ番号を計算（最大値 + 1）
  const nextChartNo = useMemo(() => {
    let maxN = 0;
    for (const c of (customers || [])) {
      const n = parseInt(c.chartNo || '0', 10);
      if (Number.isFinite(n) && n > maxN) maxN = n;
    }
    return String(maxN + 1);
  }, [customers]);

  const handleAddNew = () => {
    setEditing({
      customer: {
        id: 'cust_' + Date.now() + '_' + Math.random().toString(36).slice(2, 7),
        chartNo: nextChartNo, // 🔢 自動採番（最大値+1）
        name: '',
        kana: '',
        tel: '',
        phone2: '',
        email: '',
        postalCode: '',
        address: '',
        birthday: '',
        gender: '',
        age: 0,
        acquisitionSource: '',
        tags: [],
        note: '',
        preferredStaffId: '',
        visits: 0,
        totalSpent: 0,
        firstVisit: '',
        lastVisit: '',
      },
      isNew: true,
    });
  };

  // モバイルFAB「+」が顧客追加を呼べるよう、ハンドラを ref に登録
  React.useEffect(() => {
    if (!addHandlerRef) return;
    addHandlerRef.current = handleAddNew;
    return () => { addHandlerRef.current = null; };
  });

  const handleStartBooking = (customer) => {
    setOpen(null);
    if (onAddBookingFor) onAddBookingFor(customer);
  };

  const handleDeleteCustomer = async (customer) => {
    // customer_id でのみ判定（同姓同名の別顧客の予約を巻き込まない）
    const linkedBookings = bookings.filter(b => b.customerId === customer.id);
    if (!confirm(`${customer.name} 様を削除しますか？\n\n関連予約 ${linkedBookings.length}件あります。`)) return;
    let cascade = false;
    if (linkedBookings.length > 0) {
      cascade = confirm(
        `関連予約 ${linkedBookings.length}件 も一緒に削除しますか？\n\n` +
        `[OK] 顧客と予約を全部削除（テストデータの完全削除）\n` +
        `[キャンセル] 顧客のみ削除（予約は履歴として残す）`
      );
    }
    try {
      const API_BASE = D.API_BASE;
      const url = cascade
        ? `${API_BASE}/api/customers/${customer.id}?cascade=1`
        : `${API_BASE}/api/customers/${customer.id}`;
      const r = await D.apiFetch(url, { method: 'DELETE' });
      if (!r.ok) throw new Error(`削除失敗: ${r.status}`);
      // ローカル state 更新
      onSetCustomers(prev => prev.filter(c => c.id !== customer.id));
      setOpen(null);
      // cascade なら全予約を再取得して反映
      if (cascade) {
        try {
          const br = await D.apiFetch(`${API_BASE}/api/bookings`);
          const fresh = await br.json();
          // bookings は app.jsx 管理だが、Storage cache をリフレッシュすれば購読経由で反映される
          if (window.Storage?.getBookings) {
            // cache 更新を促す（次回 useEffect で取得し直し、unrelated）
          }
          // シンプルに reload
          setTimeout(() => window.location.reload(), 200);
        } catch (e) {}
      }
    } catch (e) {
      alert('削除に失敗しました: ' + e.message);
    }
  };

  const handleSellTicket = (customer, type) => {
    const def = TICKET_DEFS[type];
    if (!def) return;
    const label = type === 'ticket10' ? `回数券10回 ¥${def.totalPrice.toLocaleString()}` : `月額プラン（4回/月）¥${def.monthlyPrice.toLocaleString()}`;
    if (!confirm(`${customer.name} 様に「${label}」を新規販売しますか？\n\n※ これは新規購入用ボタンです。今日のご来院を1回消費する場合は「来店済」マークまたは ＋− ボタンを使ってください。\n\n売上は1回利用ごとに按分計上されます（1回 ¥${def.pricePerVisit.toLocaleString()}）。`)) return;
    const newTicket = type === 'ticket10' ? createTicket10() : createMonthly4();
    const updated = {
      ...customer,
      tickets: [...(customer.tickets || []), newTicket],
    };
    handleSaveCustomer(updated);
    setOpen(updated);
  };

  // 既存ticketのusedCount調整（移行・誤クリック修正用）
  const handleAdjustTicket = (customer, ticketId, delta) => {
    const tickets = (customer.tickets || []).map(t => {
      if (t.id !== ticketId) return t;
      if (t.type === 'ticket10') {
        const next = Math.min(t.totalCount, Math.max(0, (t.usedCount || 0) + delta));
        return { ...t, usedCount: next };
      }
      if (t.type === 'monthly4') {
        const total = t.monthlyTotal || 4;
        const next = Math.min(total, Math.max(0, (t.cycleUsed || 0) + delta));
        return { ...t, cycleUsed: next };
      }
      return t;
    });
    const updated = { ...customer, tickets };
    handleSaveCustomer(updated);
    setOpen(updated);
  };

  // ticket削除（誤販売ロールバック用）
  const handleDeleteTicket = (customer, ticketId) => {
    if (!confirm(`このチケット/プランを削除しますか？\n（誤って「新規販売」した時のロールバック用です）`)) return;
    const tickets = (customer.tickets || []).filter(t => t.id !== ticketId);
    const updated = { ...customer, tickets };
    handleSaveCustomer(updated);
    setOpen(updated);
  };

  const checkDuplicates = async () => {
    try {
      const API_BASE = D.API_BASE;
      const r = await D.apiFetch(`${API_BASE}/api/customers/duplicates`);
      const dups = await r.json();
      setDupModal(dups);
    } catch (e) {
      alert('重複チェックに失敗しました: ' + e.message);
    }
  };

  const handleMerged = async (primaryId, duplicateIds) => {
    try {
      const API_BASE = D.API_BASE;
      await D.apiFetch(`${API_BASE}/api/customers/merge`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ primaryId, duplicateIds }),
      });
      // ローカル状態も更新（D1から再取得すれば確実）
      const r = await D.apiFetch(`${API_BASE}/api/customers`);
      const fresh = await r.json();
      onSetCustomers(() => fresh);
      // 重複モーダルも再取得
      const r2 = await D.apiFetch(`${API_BASE}/api/customers/duplicates`);
      setDupModal(await r2.json());
    } catch (e) {
      alert('統合に失敗しました: ' + e.message);
    }
  };

  return (
    <div>
      <div className="stats-grid" style={{gridTemplateColumns:'repeat(3, 1fr)'}}>
        <div className="stat-tile">
          <div className="stat-tile-icon"><Icon name="users" size={18}/></div>
          <div className="stat-tile-label">登録顧客</div>
          <div className="stat-tile-value">{customers.length}<span className="unit">名</span></div>
        </div>
        <div className="stat-tile">
          <div className="stat-tile-icon orange"><Icon name="star" size={18}/></div>
          <div className="stat-tile-label">リピート率</div>
          <div className="stat-tile-value">{repeatRate}<span className="unit">%</span></div>
          <div className="stat-tile-delta">{repeaters} 名がリピート</div>
        </div>
        <div className="stat-tile">
          <div className="stat-tile-icon amber"><Icon name="yen" size={18}/></div>
          <div className="stat-tile-label">累計売上</div>
          <div className="stat-tile-value">¥{totalRevenue.toLocaleString()}</div>
        </div>
      </div>

      <div className="filter-bar">
        <div className="cal-view-switch" style={{flexShrink:0}}>
          <button className={stageFilter==='all'?'active':''} onClick={() => setStageFilter('all')}>すべて ({stageCounts.all})</button>
          <button className={stageFilter==='first'?'active':''} onClick={() => setStageFilter('first')}>初回のみ ({stageCounts.first})</button>
          <button className={stageFilter==='second'?'active':''} onClick={() => setStageFilter('second')}>2回目 ({stageCounts.second})</button>
          <button className={stageFilter==='regular'?'active':''} onClick={() => setStageFilter('regular')}>常連 ({stageCounts.regular})</button>
          <button className={stageFilter==='none'?'active':''} onClick={() => setStageFilter('none')}>未来院 ({stageCounts.none})</button>
        </div>
        <div className="search-input">
          <Icon name="search" size={15}/>
          <input className="input" placeholder="名前・カナ・電話・メールで検索" value={q} onChange={e => setQ(e.target.value)}/>
        </div>
        <div style={{flex:1}}/>
        <button className="btn" onClick={checkDuplicates} style={{marginRight:8}}>
          <Icon name="users" size={14}/> 重複チェック
        </button>
        <button className="btn primary" onClick={handleAddNew}>
          <Icon name="plus" size={14}/> 顧客を追加
        </button>
      </div>

      {/* リテンション（最終来院）フィルタ */}
      <div className="filter-bar" style={{marginTop:-4, marginBottom:10}}>
        <span style={{fontSize:11.5, color:'var(--ink-mute)', marginRight:4, alignSelf:'center'}}>最終来院:</span>
        <div className="cal-view-switch" style={{flexShrink:0}}>
          <button className={retentionFilter==='all'?'active':''} onClick={() => setRetentionFilter('all')}>すべて</button>
          <button className={retentionFilter==='active'?'active':''} onClick={() => setRetentionFilter('active')}
            style={retentionFilter==='active'?{}:{color:'var(--green-700)'}}>
            アクティブ ({retentionCounts.active})
          </button>
          <button className={retentionFilter==='warm'?'active':''} onClick={() => setRetentionFilter('warm')}
            style={retentionFilter==='warm'?{}:{color:'#a87320'}}>
            休眠予備 ({retentionCounts.warm})
          </button>
          <button className={retentionFilter==='cool'?'active':''} onClick={() => setRetentionFilter('cool')}
            style={retentionFilter==='cool'?{}:{color:'#d6651e'}}>
            離脱予備 ({retentionCounts.cool})
          </button>
          <button className={retentionFilter==='lost'?'active':''} onClick={() => setRetentionFilter('lost')}
            style={retentionFilter==='lost'?{}:{color:'var(--red)'}}>
            離脱 ({retentionCounts.lost})
          </button>
        </div>
        <div style={{fontSize:11, color:'var(--ink-mute)', alignSelf:'center', marginLeft:8}}>
          直近3ヶ月=アクティブ／3〜6=休眠予備／6〜12=離脱予備／1年以上=離脱
        </div>
      </div>

      <div className="card">
        {filtered.length === 0 ? (
          <EmptyState icon="users" title="該当する顧客がありません" sub="「顧客を追加」から登録できます"/>
        ) : (
          <div className="table-wrap">
            <table className="table">
              <thead>
                <tr>
                  {(() => {
                    const SortableTh = ({ k, label, width }) => {
                      const isActive = sortKey === k;
                      const arrow = isActive ? (sortDir === 'asc' ? ' ▲' : ' ▼') : '';
                      const onClick = () => {
                        if (isActive) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
                        else { setSortKey(k); setSortDir(k === 'chartNo' || k === 'name' ? 'asc' : 'desc'); }
                      };
                      return (
                        <th style={{width, cursor:'pointer', userSelect:'none', whiteSpace:'nowrap',
                          color: isActive ? 'var(--green-900)' : undefined,
                          fontWeight: isActive ? 800 : undefined}}
                          onClick={onClick} title="クリックで並び替え">
                          {label}{arrow}
                        </th>
                      );
                    };
                    return <>
                      <SortableTh k="chartNo" label="カルテNo" width={80}/>
                      <SortableTh k="name" label="お客様" />
                      <th style={{width:90}}>ステージ</th>
                      <th>連絡先</th>
                      <th style={{width:110}}>流入経路</th>
                      <SortableTh k="age" label="年齢" width={60}/>
                      <SortableTh k="visits" label="来院数" width={75}/>
                      <SortableTh k="lastVisit" label="最終来院" width={150}/>
                      <SortableTh k="totalSpent" label="累計売上" width={110}/>
                      <th style={{width:130}}>アクション</th>
                    </>;
                  })()}
                </tr>
              </thead>
              <tbody>
                {filtered.map(c => {
                  const stage = customerStage(c);
                  return (
                    <tr key={c.id} className="clickable" onClick={() => setOpen(c)}>
                      <td className="mono" style={{fontSize:12.5, fontWeight:600}}>{c.chartNo || <span className="text-muted">—</span>}</td>
                      <td>
                        <div style={{fontWeight:600}}>{c.name || '—'} 様</div>
                        <div className="sub">{[c.kana, c.gender, c.age ? c.age + '歳' : ''].filter(Boolean).join(' ・ ')}</div>
                      </td>
                      <td>
                        <span className="pill" style={{...STAGE_STYLE[stage], fontWeight:700, fontSize:11}}>
                          {STAGE_LABEL[stage]}
                        </span>
                      </td>
                      <td>
                        <div className="mono" style={{fontSize:12.5}}>{c.tel || c.phone || '—'}</div>
                        <div className="sub">{c.email || ''}</div>
                      </td>
                      <td><span className="text-muted" style={{fontSize:12}}>{c.acquisitionSource || '—'}</span></td>
                      <td className="mono">{c.age ? `${c.age}歳` : <span className="text-muted">—</span>}</td>
                      <td className="mono" style={{fontWeight:700}}>{c.visits || 0}回</td>
                      <td>
                        {c.lastVisit ? (() => {
                          const lv = new Date(c.lastVisit);
                          const today = new Date();
                          const lvYear = lv.getFullYear();
                          const curYear = today.getFullYear();
                          const months = (today.getFullYear() - lvYear) * 12 + (today.getMonth() - lv.getMonth());
                          // 年の色分け：今年=緑、去年=オレンジ、一昨年=赤、それ以前=濃い赤
                          let yearColor = '#14532d'; // 今年（濃い緑）
                          let yearBg = '#dcfce7';
                          if (curYear - lvYear === 1) { yearColor = '#9a3412'; yearBg = '#fed7aa'; }
                          else if (curYear - lvYear === 2) { yearColor = '#991b1b'; yearBg = '#fecaca'; }
                          else if (curYear - lvYear >= 3) { yearColor = '#fff'; yearBg = '#7f1d1d'; }
                          return (
                            <div style={{fontSize:12.5, lineHeight:1.3}}>
                              <span style={{display:'inline-block', padding:'2px 6px', background:yearBg, color:yearColor, borderRadius:4, fontWeight:700, fontSize:11.5, marginRight:4}}>
                                {lvYear}年
                              </span>
                              <span style={{color:'var(--ink-soft)'}}>
                                {(lv.getMonth()+1) + '/' + lv.getDate() + '（' + ['日','月','火','水','木','金','土'][lv.getDay()] + '）'}
                              </span>
                              {months >= 6 && (
                                <div style={{fontSize:10.5, color:'#991b1b', marginTop:1}}>
                                  {months >= 12 ? `🚨 ${Math.floor(months/12)}年以上前` : `⚠️ ${months}ヶ月前`}
                                </div>
                              )}
                            </div>
                          );
                        })() : <span className="text-muted">—</span>}
                      </td>
                      <td className="mono">¥{(c.totalSpent || 0).toLocaleString()}</td>
                      <td onClick={(e) => e.stopPropagation()}>
                        <button
                          className="btn"
                          style={{padding:'4px 10px', fontSize:12}}
                          onClick={(e) => { e.stopPropagation(); handleStartBooking(c); }}
                        >
                          <Icon name="plus" size={12}/> 予約を取る
                        </button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>

      {open && (
        <CustomerDetailModal
          customer={open}
          bookings={bookings.filter(b => b.customerId === open.id || b.name === open.name)}
          onClose={() => setOpen(null)}
          onEdit={() => setEditing({ customer: open, isNew: false })}
          onStartBooking={() => handleStartBooking(open)}
          onSellTicket={(type) => handleSellTicket(open, type)}
          onAdjustTicket={(ticketId, delta) => handleAdjustTicket(open, ticketId, delta)}
          onDeleteTicket={(ticketId) => handleDeleteTicket(open, ticketId)}
          onDelete={() => handleDeleteCustomer(open)}
        />
      )}

      {editing && (
        <CustomerEditModal
          customer={editing.customer}
          isNew={editing.isNew}
          settings={settings}
          onClose={() => setEditing(null)}
          onSave={handleSaveCustomer}
        />
      )}

      {dupModal && (
        <DuplicatesModal
          duplicates={dupModal}
          onClose={() => setDupModal(null)}
          onMerge={handleMerged}
        />
      )}
    </div>
  );
}

function CustomerDetailModal({ customer, bookings, onClose, onEdit, onStartBooking, onSellTicket, onAdjustTicket, onDeleteTicket, onDelete }) {
  const sorted = bookings.slice().sort((a, b) => (b.date || '').localeCompare(a.date || ''));
  const tags = Array.isArray(customer.tags) ? customer.tags : [];
  const todayISO = D.isoDate(new Date());
  const allTickets = Array.isArray(customer.tickets) ? customer.tickets : [];
  const activeTickets = getActiveTickets(customer, todayISO);
  const expiredOrUsed = allTickets.filter(t => !activeTickets.some(at => at.id === t.id));
  // 📋 カウンセリングシート URL（店舗設定から取得）
  const counselingUrl = (() => {
    try {
      const s = D.loadJSON(D.STORAGE_SETTINGS, {});
      return s.counselingFormUrl || '';
    } catch (e) { return ''; }
  })();

  // 📧 カウンセリングシート催促メール（Worker 経由で自動送信）
  const sendCounselingEmail = async () => {
    if (!customer.email) {
      alert(`${customer.name}様のメールアドレスが登録されていません。\n顧客編集画面でメールを登録するか、「URLコピー」でLINE等にお送りください。`);
      return;
    }
    const ok = confirm(`${customer.name} 様（${customer.email}）にカウンセリングシート記入のお願いメールを送信します。\n\n送信元：フィジオサロンキムラ\nよろしいですか？`);
    if (!ok) return;
    try {
      const res = await D.apiFetch(`${D.API_BASE}/api/send-counseling-reminder`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: customer.name, email: customer.email }),
      });
      if (!res.ok) {
        const errData = await res.json().catch(() => ({}));
        alert(`❌ メール送信失敗: ${errData.error || res.status}`);
        return;
      }
      alert(`✅ ${customer.name} 様にカウンセリングシート記入のお願いメールを送信しました。`);
    } catch (e) {
      alert(`❌ 通信エラー: ${e.message}`);
    }
  };

  // 📋 URL クリップボードコピー（LINE用）
  const copyCounselingUrl = async () => {
    if (!counselingUrl) {
      alert('店舗設定で「カウンセリングシート（GoogleフォームURL）」を登録してください。');
      return;
    }
    try {
      await navigator.clipboard.writeText(counselingUrl);
      alert(`📋 URLをクリップボードにコピーしました。\n\nLINEに貼り付けて、${customer.name}様にお送りください。\n\n${counselingUrl}`);
    } catch (e) {
      prompt('以下のURLをコピーしてLINEなどでお送りください：', counselingUrl);
    }
  };
  return (
    <Modal
      title={(customer.name || '—') + ' 様 の詳細'}
      onClose={onClose}
      size="lg"
      footer={
        <>
          {onDelete && <button className="btn danger left" onClick={onDelete}><Icon name="trash" size={14}/> 顧客を削除</button>}
          <button className="btn" onClick={onEdit}><Icon name="edit" size={14}/> 編集</button>
          {customer.email && (
            <button className="btn primary" onClick={sendCounselingEmail} title={`${customer.email}宛にGmail下書きを作成`}>
              <Icon name="mail" size={14}/> 📧 メール催促
            </button>
          )}
          <button className="btn" onClick={copyCounselingUrl} title="URLをクリップボードにコピー（LINE用）">
            <Icon name="check" size={14}/> 📋 URLコピー
          </button>
          {onSellTicket && (
            <button className="btn" onClick={() => onSellTicket('ticket10')} title="新しい回数券（10回券）を販売します">
              <Icon name="plus" size={14}/> 新規販売：回数券10回
            </button>
          )}
          <button className="btn primary" onClick={onStartBooking}><Icon name="plus" size={14}/> 予約を取る</button>
          <button className="btn" onClick={onClose}>閉じる</button>
        </>
      }
    >
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:18}}>
        <div>
          <h4 className="card-title" style={{margin:'0 0 10px'}}>基本情報</h4>
          <dl className="detail-grid">
            <dt>カルテNo</dt><dd className="mono" style={{fontWeight:700}}>{customer.chartNo || <span className="text-muted">—</span>}</dd>
            <dt>お名前</dt><dd>{customer.name}（{customer.kana || '—'}）</dd>
            <dt>性別 / 年齢</dt><dd>{customer.gender || '—'} ・ {customer.age || '—'}歳</dd>
            <dt>生年月日</dt><dd>{customer.birthday || '—'}</dd>
            <dt>電話</dt><dd className="mono">{customer.tel || customer.phone || '—'}</dd>
            {(customer.phone2) && <><dt>電話2</dt><dd className="mono">{customer.phone2}</dd></>}
            <dt>メール</dt><dd>{customer.email || '—'}</dd>
            <dt>住所</dt><dd>{customer.postalCode ? `〒${customer.postalCode} ` : ''}{customer.address || '—'}</dd>
            <dt>流入経路</dt><dd>{customer.acquisitionSource || '—'}</dd>
            <dt>📄 領収書</dt><dd>{customer.needsReceipt ? <strong style={{color:'#a87320'}}>✓ 必要</strong> : <span className="text-muted">不要</span>}</dd>
            <dt>💳 支払い</dt><dd>{customer.paymentMethod || <span className="text-muted">—</span>}</dd>
            <dt>初回来院</dt><dd>{customer.firstVisit ? D.fmtDateShortJa(customer.firstVisit) : '—'}</dd>
            <dt>最終来院</dt><dd>{customer.lastVisit ? D.fmtDateShortJa(customer.lastVisit) : '—'}</dd>
            <dt>来院回数</dt><dd style={{fontWeight:700, color:'var(--green-900)'}}>{customer.visits || 0} 回</dd>
            <dt>累計売上</dt><dd className="mono" style={{fontWeight:700}}>¥{(customer.totalSpent || 0).toLocaleString()}</dd>
            <dt>タグ</dt><dd>{tags.length === 0 ? <span className="text-muted">―</span> : tags.map(t => <span key={t} className={'pill ' + (t === 'VIP' ? 'confirmed' : 'new')} style={{marginRight:4}}>{t}</span>)}</dd>
          </dl>
          {customer.pendingMemo && (
            <>
              <h4 className="card-title" style={{margin:'18px 0 8px', color:'#a87320'}}>📝 次回宿題・特記事項</h4>
              <div style={{background:'#fdf0d6', padding:'12px 14px', borderRadius:8, fontSize:13, lineHeight:1.7, color:'#5a3d10', whiteSpace:'pre-wrap', border:'2px solid #f0c890', fontWeight:600}}>
                {customer.pendingMemo}
              </div>
            </>
          )}
          {customer.note && (
            <>
              <h4 className="card-title" style={{margin:'18px 0 8px'}}>カルテメモ</h4>
              <div style={{background:'var(--green-50)', padding:'10px 12px', borderRadius:8, fontSize:13, lineHeight:1.7, color:'var(--ink-soft)', whiteSpace:'pre-wrap'}}>{customer.note}</div>
            </>
          )}

          <h4 className="card-title" style={{margin:'18px 0 8px'}}>回数券</h4>
          {activeTickets.length === 0 && expiredOrUsed.length === 0 ? (
            <div className="text-muted" style={{fontSize:12}}>保有なし。下のボタンから販売できます。</div>
          ) : (
            <div style={{display:'grid', gap:6}}>
              {activeTickets.map(t => {
                const def = TICKET_DEFS[t.type];
                return (
                  <div key={t.id} style={{padding:'8px 12px', background:'#e8f0e6', border:'1px solid #c8d8be', borderRadius:8, fontSize:12.5}}>
                    <div style={{display:'flex', justifyContent:'space-between', alignItems:'center'}}>
                      <span style={{fontWeight:700}}>{def?.label || t.type}</span>
                      <span className="mono" style={{color:'var(--green-900)', fontWeight:700}}>{ticketRemainingLabel(t, todayISO)}</span>
                    </div>
                    <div className="sub" style={{fontSize:11, marginTop:2}}>
                      {t.type === 'ticket10' ? `購入: ${t.purchasedAt}` : `契約: ${t.contractedAt}`} ・ 1回 ¥{(t.pricePerVisit || 0).toLocaleString()}
                    </div>
                    {(onAdjustTicket || onDeleteTicket) && (
                      <div style={{display:'flex', gap:6, marginTop:6, alignItems:'center', flexWrap:'wrap'}}>
                        {onAdjustTicket && (
                          <>
                            <button
                              className="btn"
                              style={{padding:'2px 10px', fontSize:11}}
                              onClick={() => onAdjustTicket(t.id, -1)}
                              title="使用回数を1減らす（誤クリック取消）"
                            >− 1回戻す</button>
                            <button
                              className="btn"
                              style={{padding:'2px 10px', fontSize:11}}
                              onClick={() => onAdjustTicket(t.id, +1)}
                              title="使用回数を1増やす"
                            >＋ 1回使用</button>
                          </>
                        )}
                        {onDeleteTicket && (
                          <button
                            className="btn danger"
                            style={{padding:'2px 10px', fontSize:11, marginLeft:'auto'}}
                            onClick={() => onDeleteTicket(t.id)}
                            title="このチケットを削除（誤販売の取消用）"
                          >削除</button>
                        )}
                      </div>
                    )}
                  </div>
                );
              })}
              {expiredOrUsed.map(t => {
                const def = TICKET_DEFS[t.type];
                return (
                  <div key={t.id} style={{padding:'8px 12px', background:'#f5f5f0', border:'1px dashed var(--line)', borderRadius:8, fontSize:12, color:'var(--ink-mute)'}}>
                    <span style={{fontWeight:600}}>{def?.label || t.type}</span>
                    <span style={{marginLeft:8}}>使用済 / 期限切れ</span>
                  </div>
                );
              })}
            </div>
          )}
        </div>
        <div>
          <h4 className="card-title" style={{margin:'0 0 10px'}}>予約履歴</h4>
          {sorted.length === 0 ? (
            <div className="text-muted" style={{fontSize:12}}>予約履歴はまだありません</div>
          ) : (
            <div style={{maxHeight:340, overflowY:'auto', border:'1px solid var(--line)', borderRadius:8}}>
              {sorted.map(b => (
                <div key={b.id} style={{padding:'10px 12px', borderBottom:'1px solid var(--line-soft)', display:'flex', alignItems:'center', gap:10}}>
                  <div style={{flex:1}}>
                    <div style={{fontSize:12.5, fontWeight:600}}>{D.fmtDateShortJa(b.date)} {b.time}</div>
                    <div className="sub">{b.menu}</div>
                  </div>
                  <StatusPill status={b.status}/>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </Modal>
  );
}

// 顧客追加・編集モーダル
function CustomerEditModal({ customer, isNew, settings, onClose, onSave }) {
  const adChannels = getActiveAdChannels(settings);
  const ACQUISITION_SOURCES = [
    { value: '', label: '— 未選択 —' },
    ...adChannels.map(c => ({ value: c.label, label: c.label })),
  ];
  // 既存値が削除済みチャネルだった場合、選択肢に残して表示
  if (customer?.acquisitionSource && !ACQUISITION_SOURCES.find(o => o.value === customer.acquisitionSource)) {
    ACQUISITION_SOURCES.push({ value: customer.acquisitionSource, label: customer.acquisitionSource + '（削除済）' });
  }
  const [form, setForm] = useState(() => ({ ...customer }));
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  // 生年月日 → 年齢を自動計算（手動編集も可）
  const calcAge = (birthday) => {
    if (!birthday) return 0;
    try {
      const b = new Date(birthday);
      if (isNaN(b.getTime())) return 0;
      const today = new Date();
      let age = today.getFullYear() - b.getFullYear();
      const m = today.getMonth() - b.getMonth();
      if (m < 0 || (m === 0 && today.getDate() < b.getDate())) age--;
      return age;
    } catch (e) { return 0; }
  };
  const onBirthdayChange = (v) => {
    setForm(f => ({ ...f, birthday: v, age: calcAge(v) || f.age }));
  };

  // タグはカンマ区切り編集
  const tagsStr = (form.tags || []).join(', ');
  const onTagsChange = (s) => {
    const arr = s.split(',').map(x => x.trim()).filter(Boolean);
    set('tags', arr);
  };

  const handleSave = () => {
    if (!form.name || !form.name.trim()) { alert('お名前を入力してください'); return; }
    onSave({ ...form, name: form.name.trim() });
  };

  return (
    <Modal
      title={isNew ? '顧客を追加' : '顧客情報を編集'}
      onClose={onClose}
      size="lg"
      footer={
        <>
          <button className="btn" onClick={onClose}>キャンセル</button>
          <button className="btn primary" onClick={handleSave}><Icon name="check" size={14}/> 保存</button>
        </>
      }
    >
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:14}}>
        <div className="field" style={{margin:0, gridColumn:'1 / -1'}}>
          <label className="field-label">カルテ番号 {isNew && <span style={{fontSize:11, color:'var(--green-900)', fontWeight:600}}>（自動採番済み・必要に応じて編集可）</span>}</label>
          <input className="input" value={form.chartNo || ''} onChange={e => set('chartNo', e.target.value)} placeholder="例：A-001 / 0042 / 紙カルテと同じ番号"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">お名前 *</label>
          <input className="input" value={form.name || ''} onChange={e => set('name', e.target.value)} placeholder="山田 太郎"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">フリガナ</label>
          <input className="input" value={form.kana || ''} onChange={e => set('kana', e.target.value)} placeholder="ヤマダ タロウ"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">電話番号</label>
          <input className="input" value={form.tel || form.phone || ''} onChange={e => { set('tel', e.target.value); set('phone', e.target.value); }} placeholder="090-1234-5678" type="tel"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">電話番号2（任意）</label>
          <input className="input" value={form.phone2 || ''} onChange={e => set('phone2', e.target.value)} type="tel"/>
        </div>
        <div className="field" style={{margin:0, gridColumn:'span 2'}}>
          <label className="field-label">メール</label>
          <input className="input" value={form.email || ''} onChange={e => set('email', e.target.value)} type="email"/>
        </div>

        <div className="field" style={{margin:0}}>
          <label className="field-label">郵便番号</label>
          <input className="input" value={form.postalCode || ''} onChange={e => set('postalCode', e.target.value)} placeholder="123-4567"/>
        </div>
        <div className="field" style={{margin:0, gridColumn:'span 2', gridColumnStart: 'auto'}}>
          <label className="field-label">住所</label>
          <input className="input" value={form.address || ''} onChange={e => set('address', e.target.value)} placeholder="東京都中央区銀座1-2-3"/>
        </div>

        <div className="field" style={{margin:0}}>
          <label className="field-label">生年月日</label>
          <input className="input" value={form.birthday || ''} onChange={e => onBirthdayChange(e.target.value)} type="date"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">年齢</label>
          <input className="input" value={form.age || 0} onChange={e => set('age', parseInt(e.target.value) || 0)} type="number" min="0" max="120"/>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">性別</label>
          <select className="select" value={form.gender || ''} onChange={e => set('gender', e.target.value)}>
            <option value="">— 未選択 —</option>
            <option value="男性">男性</option>
            <option value="女性">女性</option>
            <option value="その他">その他</option>
          </select>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">流入経路</label>
          <select className="select" value={form.acquisitionSource || ''} onChange={e => set('acquisitionSource', e.target.value)}>
            {ACQUISITION_SOURCES.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
          </select>
        </div>

        {/* 📄 領収書・💳 支払い方法 */}
        <div className="field" style={{margin:0}}>
          <label className="field-label">📄 領収書</label>
          <label style={{display:'flex', alignItems:'center', gap:6, padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, cursor:'pointer', background: form.needsReceipt ? '#fdf6e8' : '#fff'}}>
            <input
              type="checkbox"
              checked={!!form.needsReceipt}
              onChange={e => set('needsReceipt', e.target.checked)}
            />
            <span style={{fontSize:13.5, fontWeight: form.needsReceipt ? 700 : 400, color: form.needsReceipt ? '#a87320' : 'var(--ink-mute)'}}>
              {form.needsReceipt ? '✓ 領収書 必要' : '領収書 不要'}
            </span>
          </label>
        </div>
        <div className="field" style={{margin:0}}>
          <label className="field-label">💳 支払い方法</label>
          <select className="select" value={form.paymentMethod || ''} onChange={e => set('paymentMethod', e.target.value)}>
            <option value="">— 未選択 —</option>
            <option value="現金">現金</option>
            <option value="カード">クレジットカード</option>
            <option value="PayPay">PayPay</option>
            <option value="振込">銀行振込</option>
            <option value="その他">その他</option>
          </select>
        </div>

        <div className="field" style={{margin:0, gridColumn:'span 2'}}>
          <label className="field-label">タグ（カンマ区切り、例: VIP, 紹介者）</label>
          <input className="input" value={tagsStr} onChange={e => onTagsChange(e.target.value)} placeholder="VIP, 紹介者"/>
        </div>
        <div className="field" style={{margin:0, gridColumn:'span 2'}}>
          <label className="field-label">カルテメモ（恒常的なメモ・症状履歴）</label>
          <textarea className="textarea" value={form.note || ''} onChange={e => set('note', e.target.value)} rows={3} placeholder="症状の特徴・既往歴・好み など"/>
        </div>
        <div className="field" style={{margin:0, gridColumn:'span 2', padding:'10px 12px', background:'#fdf0d6', borderRadius:8, border:'2px solid #f0c890'}}>
          <label className="field-label" style={{color:'#a87320', fontWeight:700}}>📝 次回宿題・特記事項（次回の予約時に思い出すこと）</label>
          <textarea
            className="textarea"
            value={form.pendingMemo || ''}
            onChange={e => set('pendingMemo', e.target.value)}
            rows={3}
            placeholder="例：&#10;・次回領収書を印刷する&#10;・腰の経過を確認&#10;・新聞折込のチラシを渡す"
            style={{background:'#fff'}}
          />
          <div style={{fontSize:11, color:'#a87320', marginTop:4}}>
            💡 ここに書いた内容は <strong>ダッシュボード・予約モーダル・Googleカレンダー</strong> に表示されて、忘れない仕組みです。<br/>
            完了したら手動で削除してください。
          </div>
        </div>

        {!isNew && (
          <div style={{gridColumn:'span 2', padding:'10px 12px', background:'var(--green-50)', borderRadius:8, fontSize:12, color:'var(--ink-soft)'}}>
            <strong>来院 {form.visits || 0}回</strong> ／ 累計 ¥{(form.totalSpent || 0).toLocaleString()}
            {form.firstVisit && ` ／ 初回 ${D.fmtDateShortJa(form.firstVisit)}`}
            {form.lastVisit && ` ／ 最終 ${D.fmtDateShortJa(form.lastVisit)}`}
            <div style={{marginTop:4, fontSize:11}}>※ 来院数・累計売上は予約のステータスを「来院済」にすると自動更新されます</div>
          </div>
        )}
      </div>
    </Modal>
  );
}

// 重複顧客の統合モーダル
function DuplicatesModal({ duplicates, onClose, onMerge }) {
  const [primaryByGroup, setPrimaryByGroup] = useState({}); // { phoneDigits: primaryId }

  const setPrimary = (phoneDigits, primaryId) => {
    setPrimaryByGroup(prev => ({ ...prev, [phoneDigits]: primaryId }));
  };

  const doMerge = async (group) => {
    const primaryId = primaryByGroup[group.phoneDigits] || group.customers[0].id;
    const duplicateIds = group.customers.map(c => c.id).filter(id => id !== primaryId);
    if (duplicateIds.length === 0) { alert('統合対象がありません'); return; }
    if (!confirm(`${group.customers.length}件を1件に統合します。よろしいですか？\n\n主顧客: ${group.customers.find(c => c.id === primaryId)?.name}\n削除: ${duplicateIds.length}件`)) return;
    await onMerge(primaryId, duplicateIds);
  };

  return (
    <Modal
      title="重複顧客チェック"
      onClose={onClose}
      size="lg"
      footer={<button className="btn" onClick={onClose}>閉じる</button>}
    >
      {duplicates.length === 0 ? (
        <EmptyState icon="users" title="重複は見つかりませんでした" sub="電話番号が同じ顧客はありません"/>
      ) : (
        <>
          <div style={{padding:'10px 12px', background:'#fdf0d6', borderLeft:'4px solid #d6651e', borderRadius:6, marginBottom:14, fontSize:12.5, color:'var(--ink-soft)'}}>
            電話番号が同じ顧客のグループです。<strong>主顧客</strong> を選んで「統合」を押すと、他の顧客の予約が主顧客に紐付け直され、重複レコードが削除されます。来院数・累計売上は再計算されます。
          </div>
          {duplicates.map(group => (
            <div key={group.phoneDigits} style={{marginBottom:18, border:'1px solid var(--line)', borderRadius:8, padding:12}}>
              <div style={{fontWeight:700, marginBottom:10, fontSize:13}}>
                電話番号: <span className="mono">{group.customers[0].phone}</span>（{group.customers.length}件）
              </div>
              <table className="table" style={{margin:0, fontSize:12.5}}>
                <thead>
                  <tr>
                    <th style={{width:60}}>主顧客</th>
                    <th>名前</th>
                    <th style={{width:80}}>来院数</th>
                    <th style={{width:100}}>累計売上</th>
                    <th>登録日</th>
                  </tr>
                </thead>
                <tbody>
                  {group.customers.map(c => {
                    const isPrimary = (primaryByGroup[group.phoneDigits] || group.customers[0].id) === c.id;
                    return (
                      <tr key={c.id}>
                        <td>
                          <input type="radio" checked={isPrimary} onChange={() => setPrimary(group.phoneDigits, c.id)}/>
                        </td>
                        <td>
                          <div style={{fontWeight:600}}>{c.name}</div>
                          <div className="sub">{c.kana}</div>
                        </td>
                        <td className="mono">{c.visits || 0}回</td>
                        <td className="mono">¥{(c.totalSpent || 0).toLocaleString()}</td>
                        <td className="sub">{c.createdAt ? c.createdAt.slice(0,10) : ''}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <div style={{textAlign:'right', marginTop:10}}>
                <button className="btn primary" onClick={() => doMerge(group)}>
                  <Icon name="check" size={14}/> このグループを統合
                </button>
              </div>
            </div>
          ))}
        </>
      )}
    </Modal>
  );
}

window.CustomersPage = CustomersPage;
window.TicketHelpers = {
  TICKET_DEFS,
  createTicket10,
  createMonthly4,
  rolloverMonthly,
  getActiveTickets,
  ticketRemainingLabel,
  customerStage,
};
