/* ============================================================
   顧客一覧 / 顧客詳細
   ============================================================ */

/* 会社名の正規化（法人格・空白を除去）＝重複判定のキー */
function custNorm(s) {
  return String(s || '').replace(/株式会社|（株）|\(株\)|㈱|有限会社|合同会社|合資会社|一般社団法人|一般財団法人|公益社団法人|学校法人|医療法人社団|医療法人|社会福祉法人|特定非営利活動法人|NPO法人|\s|　/gi, '').trim().toLowerCase();
}
/* 意義のない顧客＝社名が伏字（***）・非公開・（社名不明）・空だけ。ReadyCrew取込で社名が取れなかった/非公開の行＝一覧に出さない */
function custIsMeaningless(c) {
  const s = String((c && c.company) || '').trim();
  if (!s || /非公開|社名不明/.test(s)) return true;
  return /^[\*＊?？_＿\-—…・.。\s　]+$/.test(s); // 記号・伏字のみ
}
const CUST_FILLED_FIELDS = ['contact', 'contactDept', 'tel', 'email', 'address', 'url', 'industry', 'employees', 'companyOverview', 'note'];
/* 重複顧客グループを検出：会社名（正規化）が一致する2件以上をまとめ、各グループで
   案件数最多→情報が埋まっている→id順 の顧客を残す候補(primaryId)にする */
function findCustomerDupGroups(customers, cases) {
  const caseCnt = {}; (cases || []).forEach(k => { if (k.customerId) caseCnt[k.customerId] = (caseCnt[k.customerId] || 0) + 1; });
  const filled = (c) => CUST_FILLED_FIELDS.reduce((n, k) => n + (String(c[k] || '').trim() ? 1 : 0), 0);
  const groups = {};
  // マスク名（*** 等の社名非公開プレースホルダ）は別会社同士なのでグルーピング対象外（2026-07-07 誤結合の再発防止）
  const masked = (s) => /^[\*＊?？_＿\-—…・.\s]+$/.test(s) || /非公開/.test(s);
  (customers || []).forEach(c => { const n = custNorm(c.company); if (n.length >= 2 && !masked(n)) (groups[n] = groups[n] || []).push(c); });
  return Object.entries(groups).filter(([n, arr]) => arr.length > 1).map(([n, arr]) => {
    const members = arr.map(c => ({ ...c, _cases: caseCnt[c.id] || 0, _filled: filled(c) }))
      .sort((a, b) => b._cases - a._cases || b._filled - a._filled || String(a.id).localeCompare(String(b.id)));
    return { key: n, name: members[0].company, members, primaryId: members[0].id };
  }).sort((a, b) => b.members.length - a.members.length);
}

function Customers() {
  const { cases, navigate, meetings, customers, addCustomer, currentUser, can } = useStore();
  const D = window.APP_DATA;
  const isMobile = useIsMobile();
  const [q, setQ] = React.useState('');
  const [openForm, setOpenForm] = React.useState(false);
  const [openMerge, setOpenMerge] = React.useState(false);
  const [activeOnly, setActiveOnly] = React.useState(false);
  const [mineOnly, setMineOnly] = React.useState(false);
  const [followupOnly, setFollowupOnly] = React.useState(false);
  const [sort, setSort] = React.useState({ key: 'company', dir: 'asc' }); // key: company|cases|active|won|last
  const dupGroups = React.useMemo(() => findCustomerDupGroups(customers, cases), [customers, cases]);

  // 顧客ごとの集計を一度に算出（案件数・進行中・成約・失注・成約額・最終商談）。全顧客ぶんを O(案件+商談) で。
  // 進行中＝結案でない（成約won/受注done・失注lost を除く）。成約＝isCaseWonStatus（won＋done）。
  const stats = React.useMemo(() => {
    const uid = currentUser && currentUser.id;
    const by = {};
    customers.forEach(c => (by[c.id] = { n: 0, active: 0, won: 0, lost: 0, amount: 0, lastM: null, mine: false, idle: 0, owner: null, _own: {} }));
    const now = today() + 'T23:59';
    const meetByCase = {};
    meetings.forEach(m => { (meetByCase[m.caseId] = meetByCase[m.caseId] || []).push(m.datetime); });
    cases.forEach(k => {
      const s = by[k.customerId]; if (!s) return;
      s.n++;
      const closed = isCaseWonStatus(k.status) || k.status === 'lost';
      if (isCaseWonStatus(k.status)) { s.won++; s.amount += caseWonAmount(k); }
      else if (k.status === 'lost') s.lost++;
      else s.active++;
      // アカウント担当＝案件のメイン担当の最頻（進行中案件を2倍加重）
      if (k.ownerId) s._own[k.ownerId] = (s._own[k.ownerId] || 0) + (closed ? 1 : 2);
      // 自分の顧客＝自分がメイン/サポートの案件を持つ
      if (uid && (k.ownerId === uid || (k.subIds || []).includes(uid))) s.mine = true;
      // 放置＝進行中案件の caseIdleInfo（ダッシュボードと同一の単一真実）。顧客の idle は最大日数
      const info = caseIdleInfo(k); if (info && info.idleDays > s.idle) s.idle = info.idleDays;
      const times = [...(meetByCase[k.id] || []), ...((k.meetingDocs || []).map(d => d.datetime))];
      const mt = caseMeetingAt(k); if (mt && mt <= now) times.push(mt);
      times.forEach(tt => { if (tt && (!s.lastM || tt > s.lastM)) s.lastM = tt; });
    });
    Object.keys(by).forEach(id => { const o = by[id]._own; by[id].owner = Object.keys(o).sort((a, b) => o[b] - o[a])[0] || null; });
    return by;
  }, [customers, cases, meetings, currentUser]);

  // 並び替え：ヘッダ/セレクトで key を選択。同キー再クリックで昇降トグル、別キーは自然な既定方向
  const naturalDir = { company: 'asc', last: 'desc', cases: 'desc', active: 'desc', won: 'desc' };
  const onSort = (k) => setSort(s => s.key === k ? { key: k, dir: s.dir === 'asc' ? 'desc' : 'asc' } : { key: k, dir: naturalDir[k] });
  const cmp = {
    company: (a, b) => String(a.company).localeCompare(String(b.company)),
    last: (a, b) => String(stats[a.id].lastM || '').localeCompare(String(stats[b.id].lastM || '')),
    cases: (a, b) => stats[a.id].n - stats[b.id].n,
    active: (a, b) => stats[a.id].active - stats[b.id].active,
    won: (a, b) => stats[a.id].won - stats[b.id].won,
  };
  const hiddenN = customers.filter(custIsMeaningless).length; // 一覧から除外した「社名不明/非公開」件数（透明性のため件数表示）
  let rows = customers.filter(c => {
    const s = stats[c.id];
    if (custIsMeaningless(c)) return false; // 社名が伏字/非公開/未取得の意義のない行は出さない
    if (q && !(c.company.includes(q) || c.shortName.includes(q) || (c.contact || '').includes(q))) return false;
    if (activeOnly && s.active === 0) return false;
    if (mineOnly && !s.mine) return false;
    if (followupOnly && s.idle === 0) return false;
    return true;
  });
  rows = rows.slice().sort((a, b) => cmp[sort.key](a, b) * (sort.dir === 'asc' ? 1 : -1));

  // ページネーション（20件/ページ）。ページ番号はURL(?page=N)に反映＝詳細から戻っても同じページに戻る。検索/フィルタ/並替の変更で1ページ目へ
  const PER_PAGE = 20;
  const [page, setPage] = useUrlPage([q, activeOnly, mineOnly, followupOnly, sort.key, sort.dir]);
  const totalPages = Math.max(1, Math.ceil(rows.length / PER_PAGE));
  const safePage = Math.min(page, totalPages);
  syncUrlPage(safePage);
  const pageRows = rows.slice((safePage - 1) * PER_PAGE, safePage * PER_PAGE);

  // 並び替えヘッダ（デスクトップ表）。矢印はテキスト＝アイコン名依存を避ける（feature-local）
  const SortTh = ({ label, k, align }) => (
    <div onClick={() => onSort(k)} title={t('cust.sortBy')}
      style={{ cursor: 'pointer', userSelect: 'none', display: 'flex', alignItems: 'center', gap: 3, justifyContent: align === 'right' ? 'flex-end' : 'flex-start', color: sort.key === k ? '#4a5af0' : '#9aa1ab' }}>
      <span>{label}</span><span style={{ fontSize: 9 }}>{sort.key === k ? (sort.dir === 'asc' ? '▲' : '▼') : ''}</span>
    </div>
  );

  // スマホ用カード（一覧をカード化＝cases/apo と同じ規約）
  const renderCustomerCard = (c) => {
    const s = stats[c.id];
    return (
      <div key={c.id} className="lift" onClick={() => navigate('customer', c.id)}
        style={{ background: '#fff', border: '1px solid #ecedf0', borderRadius: 12, padding: 14, cursor: 'pointer', boxShadow: '0 1px 2px rgba(20,22,40,.04)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 0 }}>
          <div style={{ width: 40, height: 40, borderRadius: 10, background: '#eef0fe', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 15, fontWeight: 700, color: '#4a5af0', flex: '0 0 auto' }}>{c.shortName.charAt(0)}</div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
              <span style={{ fontSize: 14, fontWeight: 700, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{c.company}</span>
              {s.idle > 0 && <span style={{ fontSize: 10, fontWeight: 700, color: '#b45309', background: '#fdf0db', padding: '1px 6px', borderRadius: 999, flex: '0 0 auto' }}>{t('cust.followup.badge', { d: s.idle })}</span>}
            </div>
            <div style={{ fontSize: 12, color: '#aab0ba', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.shortName}{c.industry ? ' · ' + c.industry : ''}{c.contact ? ' · ' + c.contact : ''}</div>
          </div>
          {s.owner && D.user(s.owner) && <span style={{ flex: '0 0 auto' }}><Avatar user={D.user(s.owner)} size={22} /></span>}
          <Icon name="chevronRight" size={16} stroke={2} style={{ color: '#cbd0d7', flex: '0 0 auto' }} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 11, fontSize: 12, color: '#7b828d' }}>
          <span>{t('cust.col.cases')} <b style={{ color: '#1f2430' }}>{s.n}</b></span>
          <span style={{ color: s.active ? '#4a5af0' : '#c4c9d0' }}>{t('cust.col.active')} <b>{s.active}</b></span>
          {s.won > 0 && <span style={{ color: '#15803d' }}>{t('an.col.won')} <b>{s.won}</b></span>}
          <span style={{ marginLeft: 'auto', color: s.lastM ? '#4a5af0' : '#c4c9d0', fontWeight: 500 }}>{s.lastM ? fmtDate(s.lastM, true) : '—'}</span>
        </div>
      </div>
    );
  };

  // フィルタ・トグルボタン（進行中のみ / 自分の顧客 / 要フォロー）＝feature-local
  const FilterBtn = ({ on, set, label, dot }) => (
    <button onClick={() => set(v => !v)}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 6, border: '1px solid ' + (on ? '#4a5af0' : '#e2e5ea'), background: on ? '#f4f3ff' : '#fff', color: on ? '#4a5af0' : '#6b727c', borderRadius: 8, padding: '7px 12px', fontSize: 12.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap' }}>
      <span style={{ width: 7, height: 7, borderRadius: '50%', background: on ? (dot || '#4a5af0') : '#c4c9d0' }} />{label}
    </button>
  );

  return (
    <Page title={t('page.customers')} right={(
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        {can('customerEdit') && dupGroups.length > 0 && <Button variant="default" icon="link" onClick={() => setOpenMerge(true)}>{t('cust.merge.btn')} {dupGroups.length}</Button>}
        <Button variant="default" icon="download" onClick={() => {
          const data = rows.map(c => { const s = stats[c.id];
            return [c.company, c.shortName || '', c.industry || '', c.contact || '', c.contactDept || '', c.tel || '', c.email || '', c.address || '', c.url || '', s.n, s.active, s.won]; });
          csvDownload('customers-' + today() + '.csv', ['会社名', '略称', '業界', '担当窓口', '部署', '電話', 'メール', '住所', 'URL', '案件数', '進行中', '成約'], data);
        }}>{t('btn.exportCsv')}</Button>
        {can('customerEdit') && <Button variant="primary" icon="plus" onClick={() => setOpenForm(true)}>{t('btn.newCustomer')}</Button>}
      </div>
    )}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: '#fff', border: '1px solid #e2e5ea', borderRadius: 8, padding: '7px 11px', width: isMobile ? '100%' : 280 }}>
          <Icon name="search" size={15} stroke={2} style={{ color: '#9aa1ab' }} />
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t('cust.searchPlaceholder')} style={{ border: 'none', outline: 'none', fontSize: 13, width: '100%', fontFamily: 'inherit', background: 'transparent' }} />
        </div>
        {/* フィルタ：進行中のみ / 自分の顧客 / 要フォロー */}
        <FilterBtn on={activeOnly} set={setActiveOnly} label={t('cust.filter.activeOnly')} />
        <FilterBtn on={mineOnly} set={setMineOnly} label={t('cust.filter.mine')} />
        <FilterBtn on={followupOnly} set={setFollowupOnly} label={t('cust.filter.followup')} dot="#b45309" />
        {/* スマホは並び替えをセレクトで（デスクトップは表ヘッダでソート） */}
        {isMobile && (
          <select value={sort.key + ':' + sort.dir} onChange={(e) => { const [k, d] = e.target.value.split(':'); setSort({ key: k, dir: d }); }}
            style={{ border: '1px solid #e2e5ea', borderRadius: 8, padding: '7px 10px', fontSize: 12.5, fontFamily: 'inherit', color: '#3b414b', background: '#fff' }}>
            <option value="company:asc">{t('cust.sortBy')}：{t('cust.col.company')}</option>
            <option value="last:desc">{t('cust.sortBy')}：{t('cust.col.last')}</option>
            <option value="active:desc">{t('cust.sortBy')}：{t('cust.col.active')}</option>
            <option value="cases:desc">{t('cust.sortBy')}：{t('cust.col.cases')}</option>
            <option value="won:desc">{t('cust.sortBy')}：{t('an.col.won')}</option>
          </select>
        )}
        <div style={{ marginLeft: 'auto', fontSize: 12.5, color: '#9aa1ab' }}>{rows.length} {t('unit.company')}{hiddenN > 0 && <span style={{ marginLeft: 8, color: '#c4c9d0' }}>{t('cust.hiddenMeaningless', { n: hiddenN })}</span>}</div>
      </div>

      {isMobile ? (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 12 }}>
          {pageRows.map(c => renderCustomerCard(c))}
          {rows.length === 0 && <div style={{ padding: '40px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('cust.empty')}</div>}
        </div>
      ) : (
      <div style={{ background: '#fff', border: '1px solid #ecedf0', borderRadius: 12, overflow: 'hidden', boxShadow: '0 1px 2px rgba(20,22,40,.04)' }}>
        <div style={{ overflowX: 'auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 132px 76px 76px 76px 112px 64px', padding: '11px 18px', borderBottom: '1px solid #f0f1f4', minWidth: 620,
          fontSize: 12, fontWeight: 600, letterSpacing: '.02em' }}>
          <SortTh label={t('cust.col.company')} k="company" />
          <div style={{ color: '#9aa1ab' }}>{t('cust.col.contact')}</div>
          <SortTh label={t('cust.col.cases')} k="cases" align="right" />
          <SortTh label={t('cust.col.active')} k="active" align="right" />
          <SortTh label={t('an.col.won')} k="won" align="right" />
          <SortTh label={t('cust.col.last')} k="last" align="right" />
          <div></div>
        </div>
        {pageRows.map((c, i) => {
          const s = stats[c.id];
          return (
            <div key={c.id} className="case-row" onClick={() => navigate('customer', c.id)}
              style={{ display: 'grid', gridTemplateColumns: '1fr 132px 76px 76px 76px 112px 64px', padding: '12px 18px', alignItems: 'center', minWidth: 620,
                borderBottom: i === pageRows.length - 1 ? 'none' : '1px solid #f4f5f7', cursor: 'pointer' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 0, paddingRight: 12 }}>
                <div style={{ width: 36, height: 36, borderRadius: 9, background: '#eef0fe', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13.5, fontWeight: 700, color: '#4a5af0', flex: '0 0 auto' }}>{c.shortName.charAt(0)}</div>
                <div style={{ minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
                    <span style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{c.company}</span>
                    {s.idle > 0 && <span style={{ fontSize: 10, fontWeight: 700, color: '#b45309', background: '#fdf0db', padding: '1px 6px', borderRadius: 999, flex: '0 0 auto' }}>{t('cust.followup.badge', { d: s.idle })}</span>}
                  </div>
                  <div style={{ fontSize: 12, color: '#aab0ba', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.shortName}{c.industry ? ' · ' + c.industry : ''}</div>
                </div>
              </div>
              <div style={{ fontSize: 12.5, color: '#3b414b', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.contact}</div>
              <div style={{ textAlign: 'right', fontSize: 13.5, fontWeight: 600, color: '#1f2430' }}>{s.n}</div>
              <div style={{ textAlign: 'right', fontSize: 13.5, fontWeight: 600, color: s.active > 0 ? '#4a5af0' : '#c4c9d0' }}>{s.active}</div>
              <div style={{ textAlign: 'right', fontSize: 13.5, fontWeight: 600, color: s.won > 0 ? '#15803d' : '#c4c9d0' }}>{s.won}</div>
              <div style={{ textAlign: 'right', fontSize: 12.5, color: s.lastM ? '#4a5af0' : '#c4c9d0', fontWeight: 500 }}>{s.lastM ? fmtDate(s.lastM, true) : '—'}</div>
              <div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 6 }} title={s.owner && D.user(s.owner) ? t('cust.owner') + '：' + (D.user(s.owner).name || '') : ''}>{s.owner && D.user(s.owner) ? <Avatar user={D.user(s.owner)} size={22} /> : null}<Icon name="chevronRight" size={16} stroke={2} style={{ color: '#cbd0d7' }} /></div>
            </div>
          );
        })}
        {rows.length === 0 && <div style={{ padding: '50px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('cust.empty')}</div>}
        </div>
      </div>
      )}

      {/* ページャー（20件/ページ） */}
      {totalPages > 1 && (
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 12, marginTop: 16 }}>
          <Button size="sm" variant="default" icon="chevronLeft" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={safePage === 1}>{t('btn.prev')}</Button>
          <span style={{ fontSize: 12.5, color: '#7b828d', fontWeight: 600 }}>{t('cases.pageOf', { p: safePage, t: totalPages })} <span style={{ color: '#b4bac3', fontWeight: 500 }}>{t('cases.totalCustomers', { n: rows.length })}</span></span>
          <Button size="sm" variant="default" iconRight="chevronRight" onClick={() => setPage(p => Math.min(totalPages, p + 1))} disabled={safePage === totalPages}>{t('btn.next')}</Button>
        </div>
      )}
      {openForm && <CustomerFormModal onClose={() => setOpenForm(false)} onSaved={(id) => { setOpenForm(false); navigate('customer', id); }} />}
      {openMerge && <MergeDuplicatesModal onClose={() => setOpenMerge(false)} />}
    </Page>
  );
}

/* 重複顧客の統合モーダル：会社名が重複する顧客をグループ表示し、残す顧客を選んで統合。
   統合＝案件を残す顧客へ付け替え＋重複を mergedInto で一覧から隠す（可逆・railway不要）。 */
function MergeDuplicatesModal({ onClose }) {
  const { customers, allCustomers, cases, mergeCustomers, unmergeCustomer } = useStore();
  const D = window.APP_DATA;
  const groups = React.useMemo(() => findCustomerDupGroups(customers, cases), [customers, cases]);
  const merged = (allCustomers || []).filter(c => c.mergedInto);
  const [primarySel, setPrimarySel] = React.useState({});
  const primaryOf = (g) => primarySel[g.key] || g.primaryId;
  const doGroup = (g) => { const pid = primaryOf(g); mergeCustomers(pid, g.members.filter(m => m.id !== pid).map(m => m.id)); };
  const doAll = () => {
    if (!window.confirm(t('cust.merge.runAllConfirm', { n: groups.length }))) return;
    groups.forEach(g => { const pid = primaryOf(g); mergeCustomers(pid, g.members.filter(m => m.id !== pid).map(m => m.id)); });
  };
  return (
    <Modal open onClose={onClose} title={t('cust.merge.title')} subtitle={t('cust.merge.subtitle')} width={720}
      footer={<><Button variant="subtle" onClick={onClose}>{t('btn.close')}</Button>{groups.length > 0 && <Button variant="primary" icon="link" onClick={doAll}>{t('cust.merge.runAll', { n: groups.length })}</Button>}</>}>
      {groups.length === 0 ? (
        <div style={{ padding: '44px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>
          <Icon name="check2" size={26} stroke={2} style={{ color: '#8ec89a', marginBottom: 8 }} /><div>{t('cust.merge.empty')}</div>
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ fontSize: 12.5, color: '#7b828d', fontWeight: 600 }}>{t('cust.merge.foundGroups', { n: groups.length })}</div>
          {groups.map(g => {
            const pid = primaryOf(g);
            return (
              <div key={g.key} style={{ border: '1px solid #ecedf0', borderRadius: 10, padding: 12, background: '#fff' }}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
                  {g.members.map(m => {
                    const isPrimary = m.id === pid;
                    const info = [t('cust.merge.cases', { n: m._cases }), m.contact, m.tel, m.email].filter(Boolean).join(' · ');
                    return (
                      <label key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 11px', borderRadius: 8, cursor: 'pointer',
                        background: isPrimary ? '#f4f3ff' : '#f8f9fb', border: isPrimary ? '1px solid #d9d6fb' : '1px solid transparent' }}>
                        <input type="radio" name={'merge-' + g.key} checked={isPrimary} onChange={() => setPrimarySel(s => ({ ...s, [g.key]: m.id }))} style={{ accentColor: '#4a5af0', flex: '0 0 auto' }} />
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 13, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.company}</div>
                          <div style={{ fontSize: 11.5, color: '#9aa1ab', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{info}</div>
                        </div>
                        <span style={{ fontSize: 11, fontWeight: 700, color: isPrimary ? '#4a5af0' : '#c4c9d0', flex: '0 0 auto' }}>{isPrimary ? t('cust.merge.keep') : t('cust.merge.mergeInto')}</span>
                      </label>
                    );
                  })}
                </div>
                <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 10 }}>
                  <Button size="sm" variant="primary" icon="link" onClick={() => doGroup(g)}>{t('cust.merge.doGroup')}</Button>
                </div>
              </div>
            );
          })}
        </div>
      )}
      {merged.length > 0 && (
        <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px solid #f0f1f4' }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', marginBottom: 8 }}>{t('cust.merge.mergedTitle', { n: merged.length })}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {merged.map(m => {
              const into = D.customer(m.mergedInto);
              return (
                <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 11px', borderRadius: 8, background: '#f8f9fb' }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 600, color: '#7b828d', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.company}</div>
                    <div style={{ fontSize: 11, color: '#aab0ba', marginTop: 1 }}>{t('cust.merge.mergedInto2', { name: into ? into.company : m.mergedInto })}</div>
                  </div>
                  <Button size="sm" variant="subtle" onClick={() => unmergeCustomer(m.id)}>{t('cust.merge.undo')}</Button>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </Modal>
  );
}

/* 未登録フィールドのプレースホルダ */
function Empty() {
  return <span style={{ color: '#c4c9d0', fontWeight: 400 }}>{t('cust2.unregistered')}</span>;
}

function FormSection({ title, children }) {
  return (
    <div style={{ marginBottom: 6 }}>
      <div style={{ fontSize: 12, fontWeight: 700, color: '#9aa1ab', letterSpacing: '.03em', margin: '4px 0 10px', paddingBottom: 7, borderBottom: '1px solid #f0f1f4' }}>{title}</div>
      {children}
    </div>
  );
}

const CUSTOMER_INDUSTRIES = ['製造業', 'IT・ソフトウェア', '卸売・商社', '通信・ネットワーク', '小売・流通', '建設・不動産', '金融・保険', '医療・福祉', 'その他'];
const CUSTOMER_SIZES = ['〜50名', '50〜100名', '100〜300名', '300〜500名', '500〜1,000名', '1,000名以上'];


function CustomerFormModal({ onClose, onSaved, editCustomer, initialCompany }) {
  const { addCustomer, updateCustomer } = useStore();
  const isEdit = !!editCustomer;
  const init = (k, d = '') => (editCustomer && editCustomer[k]) || d;
  const [f, setF] = React.useState({
    company: init('company', initialCompany || ''), shortName: init('shortName'), industry: init('industry'), employees: init('employees'),
    founded: init('founded'), capital: init('capital'), sales: init('sales'), fiscalMonth: init('fiscalMonth'), companyOverview: init('companyOverview'),
    url: init('url'), address: init('address'),
    contact: init('contact'), contactDept: init('contactDept'), contactGender: init('contactGender'), contactRole: init('contactRole'), tel: init('tel'), email: init('email'), contactNote: init('contactNote'),
    note: init('note'),
  });
  const set = (k) => (e) => setF(s => ({ ...s, [k]: e.target.value }));
  const submit = () => {
    if (!f.company.trim()) return;
    if (isEdit) { updateCustomer(editCustomer.id, f); onSaved(editCustomer.id); }
    else { const id = addCustomer(f); onSaved(id); }
  };
  return (
    <Modal open onClose={onClose} title={isEdit ? t('cust2.editTitle') : t('cust2.createTitle')} subtitle={isEdit ? t('cust2.editSubtitle') : t('cust2.createSubtitle')} width={640}
      footer={<><Button variant="subtle" onClick={onClose}>{t('btn.cancel')}</Button><Button variant="primary" icon="check" onClick={submit} disabled={!f.company.trim()}>{isEdit ? t('btn.update') : t('btn.create')}</Button></>}>
      <FormSection title={t('cust2.section.company')}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.company')} required><TextInput value={f.company} onChange={set('company')} placeholder={t('cust2.placeholder.company')} /></Field>
          <Field label={t('cust2.label.shortName')} hint={t('cust2.hint.shortName')}><TextInput value={f.shortName} onChange={set('shortName')} placeholder={t('cust2.placeholder.shortName')} /></Field>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.industry')}><SelectInput value={f.industry} onChange={set('industry')} options={CUSTOMER_INDUSTRIES} /></Field>
          <Field label={t('cust2.label.employees')}><SelectInput value={f.employees} onChange={set('employees')} options={CUSTOMER_SIZES} /></Field>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.founded')}><TextInput value={f.founded} onChange={set('founded')} /></Field>
          <Field label={t('cust2.label.capital')}><TextInput value={f.capital} onChange={set('capital')} /></Field>
          <Field label={t('cust2.label.sales')}><TextInput value={f.sales} onChange={set('sales')} /></Field>
          <Field label={t('cust2.label.fiscalMonth')}><TextInput value={f.fiscalMonth} onChange={set('fiscalMonth')} /></Field>
        </div>
        <Field label={t('cust2.label.website')}><TextInput value={f.url} onChange={set('url')} placeholder={t('cust2.placeholder.website')} /></Field>
        <Field label={t('cust2.label.address')}><TextInput value={f.address} onChange={set('address')} placeholder={t('cust2.placeholder.address')} /></Field>
        <Field label={t('cust2.label.companyOverview')}>
          <textarea value={f.companyOverview} onChange={set('companyOverview')} rows={2} style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.6, minHeight: 56 }} />
        </Field>
      </FormSection>
      <FormSection title={t('cust2.section.contact')}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.name')}><TextInput value={f.contact} onChange={set('contact')} placeholder={t('cust2.placeholder.name')} /></Field>
          <Field label={t('cust2.label.dept')}><TextInput value={f.contactDept} onChange={set('contactDept')} placeholder={t('cust2.placeholder.dept')} /></Field>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.phone')}><TextInput value={f.tel} onChange={set('tel')} placeholder={t('cust2.placeholder.phone')} /></Field>
          <Field label={t('cust2.label.email')}><TextInput value={f.email} onChange={set('email')} placeholder={t('cust2.placeholder.email')} /></Field>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 14 }}>
          <Field label={t('cust2.label.gender')}><TextInput value={f.contactGender} onChange={set('contactGender')} /></Field>
          <Field label={t('cust2.label.role')}><TextInput value={f.contactRole} onChange={set('contactRole')} /></Field>
        </div>
        <Field label={t('cust2.label.contactNote')} hint={t('cust2.hint.contactNote')}>
          <textarea value={f.contactNote} onChange={set('contactNote')} rows={3} style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.6, minHeight: 70 }} />
        </Field>
      </FormSection>
      <FormSection title={t('cust2.section.note')}>
        <Field label={t('cust2.label.note')} hint={t('cust2.hint.note')}>
          <textarea value={f.note} onChange={set('note')} rows={3} placeholder={t('cust2.placeholder.note')}
            style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.6, minHeight: 70 }} />
        </Field>
      </FormSection>
    </Modal>
  );
}

function CustomerDetail() {
  const { route, navigate, cases, meetings, currentUser, can } = useStore();
  const isMobile = useIsMobile();
  const D = window.APP_DATA;
  const isAdmin = currentUser.role === 'admin';
  const [openEdit, setOpenEdit] = React.useState(false);
  const c = D.customer(route.id);
  if (!c) return <Page title={t('page.customer')}><div>{t('cust2.notFound')}</div></Page>;
  const ks = cases.filter(k => k.customerId === c.id);
  const cids = ks.map(k => k.id);
  // 顧客サマリー（進行中・成約・失注・成約額）＋最終商談。進行中＝結案でない／成約＝isCaseWonStatus（won＋done）
  const stat = { active: 0, won: 0, lost: 0, amount: 0 };
  ks.forEach(k => { if (isCaseWonStatus(k.status)) { stat.won++; stat.amount += caseWonAmount(k); } else if (k.status === 'lost') stat.lost++; else stat.active++; });
  const nowD = today() + 'T23:59';
  const lastM = [...meetings.filter(m => cids.includes(m.caseId)).map(m => m.datetime), ...ks.flatMap(k => (k.meetingDocs || []).map(d => d.datetime)), ...ks.map(k => caseMeetingAt(k)).filter(mt => mt && mt <= nowD)].filter(Boolean).sort().slice(-1)[0] || null;
  // アカウント担当（案件メイン担当の最頻・進行中2倍加重）＋放置日数（進行中案件の caseIdleInfo の最大）
  const ownCnt = {};
  ks.forEach(k => { if (k.ownerId) ownCnt[k.ownerId] = (ownCnt[k.ownerId] || 0) + ((isCaseWonStatus(k.status) || k.status === 'lost') ? 1 : 2); });
  const acctOwner = Object.keys(ownCnt).sort((a, b) => ownCnt[b] - ownCnt[a])[0] || null;
  const custIdle = ks.reduce((mx, k) => { const info = caseIdleInfo(k); return info && info.idleDays > mx ? info.idleDays : mx; }, 0);
  // 案件リストは 進行中→成約→失注 の順、同順内は次回商談/更新日の新しい順
  const caseRank = (k) => isCaseWonStatus(k.status) ? 1 : (k.status === 'lost' ? 2 : 0);
  const ksSorted = ks.slice().sort((a, b) => caseRank(a) - caseRank(b) || String(caseMeetingAt(b) || b.updatedAt || '').localeCompare(String(caseMeetingAt(a) || a.updatedAt || '')));
  // タイムライン = 商談記録 + 会議記録（Fireflies取り込み分）。同一通話の重複は商談記録を優先
  const docEntries = ks.flatMap(k => (k.meetingDocs || []).map(d => ({
    id: 'md-' + d.id, caseId: k.id, datetime: d.datetime, method: 'online', authorId: k.ownerId,
    summary: (d.bullets && d.bullets[0]) || d.summary || t('cust2.docSummaryFallback', { title: d.title, duration: d.duration }), isDoc: true,
  })));
  const tl = [...meetings.filter(m => cids.includes(m.caseId)),
    ...docEntries.filter(d => !meetings.some(m => m.caseId === d.caseId && (m.datetime || '').slice(0, 10) === (d.datetime || '').slice(0, 10)))]
    .sort((a, b) => (b.datetime || '').localeCompare(a.datetime || ''));
  const methodIcon = { visit: 'mapPin', phone: 'phone', online: 'video', email: 'mail' };
  return (
    <Page title={t('page.customer')} right={can('customerEdit') ? <Button variant="default" icon="edit" onClick={() => setOpenEdit(true)}>{t('btn.edit')}</Button> : null}>
      <button onClick={() => navigate('customers')} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, border: 'none', background: 'transparent', color: '#9aa1ab', fontSize: 12.5, cursor: 'pointer', fontFamily: 'inherit', marginBottom: 14 }}>
        <Icon name="chevronLeft" size={14} stroke={2} />{t('cust2.backToList')}
      </button>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20 }}>
        <div style={{ width: 54, height: 54, borderRadius: 14, background: '#eef0fe', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20, fontWeight: 700, color: '#4a5af0' }}>{c.shortName.charAt(0)}</div>
        <div>
          <h1 style={{ fontSize: 22, fontWeight: 700, color: '#1c1f26', margin: 0 }}>{c.company}</h1>
          <div style={{ fontSize: 13, color: '#9aa1ab', marginTop: 3 }}>
            {c.shortName}{c.industry ? ` · ${c.industry}` : ''}{c.employees ? ` · ${c.employees}` : ''} · {t('cust2.caseCount', { n: ks.length })}{acctOwner && D.user(acctOwner) ? ` · ${t('cust.owner')} ${D.user(acctOwner).short || D.user(acctOwner).name || ''}` : ''}
          </div>
        </div>
      </div>
      {/* 放置（ご無沙汰）警告：進行中案件があるのに一定日数動きなし＝要フォロー */}
      {custIdle > 0 && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '10px 14px', background: '#fdf0db', border: '1px solid #f2d9a8', borderRadius: 10, marginBottom: 16, fontSize: 12.5, color: '#92500e', fontWeight: 600 }}>
          <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#b45309', flex: '0 0 auto' }} />
          {t('cust.followup.detail', { d: custIdle })}
        </div>
      )}
      {/* 顧客サマリーKPI（案件数・進行中・成約・失注・成約額・最終商談）。成約額と最終商談は値があるときだけ */}
      <div style={{ display: 'flex', gap: 10, marginBottom: 18, flexWrap: 'wrap' }}>
        {[
          [t('cust.col.cases'), ks.length, '#1f2430'],
          [t('cust.col.active'), stat.active, '#4a5af0'],
          [t('an.col.won'), stat.won, '#15803d'],
          [t('cust.stat.lost'), stat.lost, '#9aa1ab'],
          ...(stat.amount > 0 ? [[t('cust.stat.wonAmount'), '¥' + stat.amount.toLocaleString(), '#0f766e']] : []),
          ...(lastM ? [[t('cust.col.last'), fmtDate(lastM, true), '#4a5af0']] : []),
        ].map(([lb, val, col]) => (
          <div key={lb} style={{ flex: '1 1 88px', minWidth: 88, background: '#fff', border: '1px solid #ecedf0', borderRadius: 10, padding: '9px 12px' }}>
            <div style={{ fontSize: 11, color: '#9aa1ab', fontWeight: 600 }}>{lb}</div>
            <div style={{ fontSize: 19, fontWeight: 800, color: col, marginTop: 2, whiteSpace: 'nowrap' }}>{val}</div>
          </div>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '300px 1fr', gap: isMobile ? 14 : 18, alignItems: 'start' }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Card title={t('cust2.section.company')} pad={16}>
            <InfoRow icon="grid" label={t('cust2.label.industry')}>{c.industry || <Empty />}</InfoRow>
            <InfoRow icon="customers" label={t('cust2.label.employees')}>{c.employees || <Empty />}</InfoRow>
            {c.founded && <InfoRow icon="calendar" label={t('cust2.label.founded')}>{c.founded}</InfoRow>}
            {c.capital && <InfoRow icon="chart" label={t('cust2.label.capital')}>{c.capital}</InfoRow>}
            {c.sales && <InfoRow icon="chart" label={t('cust2.label.sales')}>{c.sales}</InfoRow>}
            {c.fiscalMonth && <InfoRow icon="calendar" label={t('cust2.label.fiscalMonth')}>{c.fiscalMonth}</InfoRow>}
            <InfoRow icon="link" label={t('cust2.label.website')}>{c.url && (/^https?:\/\//.test(c.url) || /[\w-]+\.[\w-]{2,}/.test(c.url)) ? <a href={/^https?:\/\//.test(c.url) ? c.url : 'https://' + c.url} target="_blank" rel="noreferrer" style={{ color: '#4a5af0', textDecoration: 'none' }}>{c.url.replace(/^https?:\/\//, '')}</a> : <Empty />}</InfoRow>
            <InfoRow icon="mapPin" label={t('cust2.label.address')}>{c.address || <Empty />}</InfoRow>
            {c.companyOverview && <div style={{ marginTop: 10, paddingTop: 10, borderTop: '1px solid #f0f1f4' }}>
              <div style={{ fontSize: 11, fontWeight: 700, color: '#9aa1ab', marginBottom: 5 }}>{t('cust2.label.companyOverview')}</div>
              <div style={{ fontSize: 12.5, color: '#3b414b', lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>{c.companyOverview}</div>
            </div>}
          </Card>
          <Card title={t('cust2.section.contact')} pad={16}>
            <InfoRow icon="user" label={t('cust2.label.name')}>{c.contact ? <>{c.contact}{c.contactDept && <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{c.contactDept}</div>}</> : <Empty />}</InfoRow>
            {c.contactGender && <InfoRow icon="user" label={t('cust2.label.gender')}>{c.contactGender}</InfoRow>}
            <InfoRow icon="phone" label={t('cust2.label.phone')}>{c.tel || <Empty />}{c.contactTel ? <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{window.t("extra.case.directPhone")}{c.contactTel}</div> : null}</InfoRow>
            <InfoRow icon="mail" label={t('cust2.label.email')}>{c.email ? <a href={'mailto:' + c.email} style={{ color: '#4a5af0', textDecoration: 'none' }}>{c.email}</a> : <Empty />}</InfoRow>
            {c.contactRole && <InfoRow icon="edit" label={t('cust2.label.role')}>{c.contactRole}</InfoRow>}
            {c.contactNote && <div style={{ marginTop: 10, paddingTop: 10, borderTop: '1px solid #f0f1f4' }}>
              <div style={{ fontSize: 11, fontWeight: 700, color: '#9aa1ab', marginBottom: 5 }}>{t('cust2.label.contactNote')}</div>
              <div style={{ fontSize: 12.5, color: '#3b414b', lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>{c.contactNote}</div>
            </div>}
          </Card>
          {c.note && (
            <Card title={t('cust2.section.note')} pad={16}>
              <div style={{ fontSize: 12.5, color: '#3b414b', lineHeight: 1.7 }}>{c.note}</div>
            </Card>
          )}
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <Card title={t('cust2.caseListTitle', { n: ks.length })} pad={0}>
            {ksSorted.map((k, i) => (
              <div key={k.id} className="row-hover" onClick={() => navigate('case', k.id)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '13px 18px', cursor: 'pointer', borderBottom: i === ksSorted.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
                <StatusBadge status={k.status} size="sm" />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430' }}>{k.title}</div>
                  <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>
                    {(() => { const mt = caseMeetingAt(k); return (mt && mt.slice(0, 10) >= today()) ? <>{t('cd.nextMeeting')} <span style={{ color: '#4a5af0' }}>{fmtDate(mt)}</span> · </> : null; })()}
                    {t('cust2.due')} {caseDue(k) ? <span style={{ color: '#4a5af0' }}>{fmtDate(caseDue(k))}</span> : '—'} · {t('cust2.updated')} <span style={{ color: '#4a5af0' }}>{relTime(k.updatedAt)}</span></div>
                </div>
                {k.ownerId && <Avatar user={D.user(k.ownerId)} size={26} />}
                <Icon name="chevronRight" size={16} stroke={2} style={{ color: '#cbd0d7' }} />
              </div>
            ))}
          </Card>
          <Card title={t('cust2.timelineTitle')} pad={18}>
            <div style={{ position: 'relative' }}>
              {tl.length > 0 && <div style={{ position: 'absolute', left: 15, top: 8, bottom: 8, width: 2, background: '#eef0f3' }} />}
              {tl.map(m => {
                const k = D.caseById(m.caseId); const au = D.user(m.authorId);
                return (
                  <div key={m.id} style={{ display: 'flex', gap: 13, marginBottom: 16, position: 'relative' }}>
                    <div style={{ width: 32, flex: '0 0 auto', display: 'flex', justifyContent: 'center', zIndex: 1 }}>
                      <div style={{ width: 32, height: 32, borderRadius: '50%', background: '#fff', border: '2px solid #eef0f3', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                        <Icon name={methodIcon[m.method] || 'video'} size={14} stroke={2} style={{ color: '#4a5af0' }} />
                      </div>
                    </div>
                    <div style={{ flex: 1 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                        <span style={{ fontSize: 12.5, fontWeight: 700, color: '#1f2430' }}>{fmtDateFull(m.datetime)} {fmtTime(m.datetime)}</span>
                        <span onClick={() => navigate('case', m.caseId)} style={{ fontSize: 12, color: '#4a5af0', background: '#eef0fe', padding: '1px 8px', borderRadius: 5, cursor: 'pointer', fontWeight: 600 }}>{k.title}</span>
                      </div>
                      <div style={{ fontSize: 12.5, color: '#3b414b', marginTop: 5, lineHeight: 1.6 }}>{m.summary}</div>
                      <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 4 }}>
                        {au ? au.short + ' · ' : ''}{(D.METHODS[m.method] || {}).label || t('cust2.methodFallbackOnline')}{m.isDoc && <span style={{ color: '#ef5a3c', fontWeight: 700 }}> · Fireflies</span>}
                      </div>
                    </div>
                  </div>
                );
              })}
              {tl.length === 0 && <div style={{ padding: '20px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>{t('cust2.noTimeline')}</div>}
            </div>
          </Card>
        </div>
      </div>
      {openEdit && <CustomerFormModal editCustomer={c} onClose={() => setOpenEdit(false)} onSaved={() => setOpenEdit(false)} />}
    </Page>
  );
}

Object.assign(window, { Customers, CustomerDetail, CustomerFormModal, FormSection, MergeDuplicatesModal, findCustomerDupGroups, custNorm });
