/* ============================================================
   アポ管理 — ReadyCrew の獲客リード（主に「未回答」）プール
   ・案件（商談管理）とは独立
   ・取込みは既存の ReadyCrew 取込みスクリプト（cases.jsx）を拡張して /api/import/leads へ
   ・A3 で AI 推薦（成件機率）＋一鍵エントリ提案信を追加予定
   ============================================================ */

/* rcStatus の表示ラベル（ReadyCrew の status 値は環境依存のため、既知のものだけ和訳・他は原文） */
function rcStatusLabel(s) {
  const m = {
    '': 'リード', '1': '未回答', UNANSWERED: '未回答', NOT_ANSWERED: '未回答', NEW: '未回答', LEAD: 'リード', LEAD_ALL: 'リード',
    '2': '商談予定', '3': '商談済', IN_PROGRESS_ALL: '商談', IN_PROGRESS: '商談中', SCHEDULED: '商談予定', APPOINTED: '商談予定', SCHEDULING: '日程調整中', RESCHEDULE: 'リスケ中', RESCHEDULING: 'リスケ中', PROPOSED: '提案中',
    DONE: '結果', DONE_ALL: '結果', ORDERED: '受注', ORDER_LOSTED: '失注', CLOSED: '受付終了', CLOSED_ALL: '受付終了',
  };
  return m[s] || s || 'リード';
}
/* ReadyCrew の詳細ステータス（statusForClient）。2026-09-01 の全件台帳化で判明したとおり、数値 status は
   「未回答(143件)」と「受付終了(955件)」がどちらも 1 に潰れてしまい区別できない。獲客管理の表示・絞り込みは
   statusForClient を正とし、無い旧データだけ従来の rcStatus ラベルにフォールバックする。 */
const RC_SFC = {
  '10': { get label(){return window.t("label.extra8");},     group: 'open',   color: '#b45309', bg: '#fdf0db' },
  '11': { get label(){return window.t("label.extra9");}, group: 'open',   color: '#b45309', bg: '#fdf0db' },
  '21': { get label(){return window.t("calendar.kind.next");},   group: 'deal',   color: '#1d4ed8', bg: '#e8f0fe' },
  '22': { get label(){return window.t("label.extra10");},   group: 'deal',   color: '#1d4ed8', bg: '#e8f0fe' },
  '23': { get label(){return window.t("extra.dashboard.negotiating");},     group: 'deal',   color: '#1d4ed8', bg: '#e8f0fe' },
  '24': { get label(){return window.t("label.extra11");},     group: 'deal',   color: '#1d4ed8', bg: '#e8f0fe' },
  '31': { get label(){return window.t("dash2.table.won");},       group: 'done',   color: '#15803d', bg: '#e3f5e9' },
  '32': { get label(){return window.t("cust.stat.lost");},       group: 'done',   color: '#b91c1c', bg: '#fdecec' },
  '41': { get label(){return window.t("apo.st.skip");},     group: 'closed', color: '#7b828d', bg: '#f0f1f4' },
  '42': { get label(){return window.t("label.extra12");},   group: 'closed', color: '#7b828d', bg: '#f0f1f4' },
  '44': { get label(){return window.t("btn.cancel");}, group: 'closed', color: '#7b828d', bg: '#f0f1f4' },
  '45': { get label(){return window.t("label.extra13");},   group: 'closed', color: '#7b828d', bg: '#f0f1f4' },
};
const rcSfc = (l) => RC_SFC[String((l && l.rcStatusForClient) || '')] || null;
/* 表示ラベル／色：statusForClient があればそれ、無ければ従来どおり数値 status のラベル */
const rcLeadStatusLabel = (l) => { const m = rcSfc(l); return m ? m.label : rcStatusLabel(l && l.rcStatus); };
const rcLeadStatusMeta = (l) => rcSfc(l) || { label: rcStatusLabel(l && l.rcStatus), group: 'open', color: '#b45309', bg: '#fdf0db' };
/* 絞り込みキー（statusForClient を優先。旧データは r+rcStatus で従来値を保持） */
const rcLeadStatusKey = (l) => (rcSfc(l) ? String(l.rcStatusForClient) : 'r' + String((l && l.rcStatus) || ''));
/* 募集の大分類：進行中（未回答〜商談）／終了（受注・失注・見送り・受付終了 等） */
const RC_GROUPS = { open: '進行中', deal: '進行中', done: '終了', closed: '終了' };

/* アポ管理に留めるリードのステータス（未回答・リード＝まだ商談化していない）。
   これ以外（商談中/商談予定/提案中/受注 等）は取込時に案件化され、案件管理に表示される（＝CRMの振り分けルール）。 */
const RC_LEAD_STATUSES = ['', '1', 'UNANSWERED', 'NOT_ANSWERED', 'NEW', 'LEAD', 'LEAD_ALL'];
const rcIsLeadStatus = (s) => RC_LEAD_STATUSES.includes(String(s || ''));
/* 商談化と確定できるステータス（DB実測の現行値：2=商談予定・3=商談済）。
   ★振り分けは「許可リスト」方式：これ以外の未知ステータスは、商談の実体（商談日 or 実施記録）が
   無い限りリード扱い（安全側）。ReadyCrew側に新ステータス値が増えるたびに未商談が案件へ
   すり抜ける再発（除外リスト方式の宿命・2026-06-22の'1'事件と2026-07-05の再発で実証）を恒久防止 */
const RC_DEAL_STATUSES = ['2', '3'];
const rcIsDealStatus = (s) => RC_DEAL_STATUSES.includes(String(s == null ? '' : s).trim());
/* case レコードが「未回答などのリード状態」か＝アポ管理に属し、案件管理に出してはいけないもの。
   ※ rcStatus が空（手動作成・発注ナビ等の正規案件）は除外＝案件として扱う。これが案件一覧/アポ管理 両方の振り分けの単一の真実。 */
const rcIsLeadCase = (c) => {
  const s = String((c && c.rcStatus) || '').trim();
  if (s === '') return false;            // 手動作成・発注ナビ等の正規案件
  if (rcIsLeadStatus(s)) return true;    // 既知のリード状態
  if (rcIsDealStatus(s)) return false;   // 既知の商談化（2/3）
  // 未知のステータス：商談日・実施記録のどちらも無ければリード＝アポ管理側（安全側に倒す）
  const hasMtg = !!(c.appointAt || c.appointAtLive || c.nextMeeting) || (typeof caseMeetingCount === 'function' && caseMeetingCount(c) > 0);
  return !hasMtg;
};
/* アポ管理リードの取得元（経由）。source==='hnavi'＝発注ナビ、それ以外は ReadyCrew（アポの既定）。色は案件一覧の経由列と統一 */
function apoViaMeta(l) {
  if (l && l.source === 'hnavi') return { key: 'hnavi', label: '発注ナビ', color: '#c2410c', bg: '#fde8db', icon: 'link' };
  return { key: 'readycrew', label: 'ReadyCrew', color: '#4a5af0', bg: '#eef0fe', icon: 'link' };
}
/* 商談化した（＝正規案件 'k'+matchingId が存在する）案件IDの集合。rcStatusが未回答のままの case は案件と見なさない */
function rcCaseIdSet(cases) { return new Set((cases || []).filter(c => !rcIsLeadCase(c)).map(c => c.id)); }
/* リードが商談化済みか（k+matchingId の正規案件が存在） */
function rcLeadBecameCase(l, caseIdSet) { return !!(l && caseIdSet && caseIdSet.has('k' + l.matchingId)); }
/* 発注ナビのリードがエントリー済みか＝発注ナビ上のステータスが「募集中」以外（選定中/選定終了/コンタクト中/商談中…はすべてエントリー後） */
function hnaviEntered(l) { const s = String((l && l.rcStatus) || '').trim(); return !!(l && l.source === 'hnavi' && s && s !== '募集中'); }
/* リードが「エントリー済み」か：商談化（自動）or 発注ナビで応募済み or 手動で応募する/応募済み。分析のエントリー率もこの定義で集計 */
function rcLeadEntered(l, caseIdSet) { return rcLeadBecameCase(l, caseIdSet) || hnaviEntered(l) || ['entry', 'applied'].includes(l && l.apoStatus); }
function scoreColor(s) { return s == null ? '#9aa1ab' : s >= 70 ? '#16a34a' : s >= 40 ? '#d97706' : '#dc2626'; }
function fmtYen(n) { if (!n || !isFinite(n)) return ''; if (n >= 1e8) { const v = n / 1e8; return (v % 1 ? v.toFixed(1) : v) + '億円'; } if (n >= 1e4) return Math.round(n / 1e4) + '万円'; return n + '円'; }

/* 一覧カラム定義（ヘッダー・行で共通）：登録日 / 場所 / 予算 / 法人名 / 案件タイトル / AI / エントリー / chevron */
const APO_GRID = {
  display: 'grid',
  gridTemplateColumns: '92px 76px 132px minmax(130px, 1fr) minmax(190px, 1.6fr) 70px 100px 116px 20px',
  columnGap: 12,
  alignItems: 'center',
};

/* アポ管理のCRM手動ステータス（取得商談チームが「応募する/見送り」を一覧で手動選択） */
const APO_STATUS_ORDER = ['none', 'entry', 'applied', 'skip'];
const APO_STATUS = {
  none:    { labelKey: 'apo.st.none',    color: '#9aa1ab', soft: '#f1f2f4', border: '#e2e5ea' },
  entry:   { labelKey: 'apo.st.entry',   color: '#4a5af0', soft: '#eef0fe', border: '#d9d8f7' },
  applied: { labelKey: 'apo.st.applied', color: '#15803d', soft: '#e3f5e9', border: '#c3e8d0' },
  skip:    { labelKey: 'apo.st.skip',    color: '#b91c1c', soft: '#fdecec', border: '#f3cdcd' },
};
const APO_ARROW = "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%239aa1ab' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>\")";
function ApoStatusSelect({ lead, size = 'sm' }) {
  const { setApoLeadStatus } = useStore();
  const cur = APO_STATUS[lead.apoStatus] ? lead.apoStatus : 'none';
  const m = APO_STATUS[cur];
  const lg = size === 'lg';
  return (
    <select value={cur} onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}
      onChange={(e) => { e.stopPropagation(); setApoLeadStatus(lead.id, e.target.value); }}
      style={{ appearance: 'none', WebkitAppearance: 'none', border: '1px solid ' + m.border, backgroundColor: m.soft,
        backgroundImage: APO_ARROW, backgroundRepeat: 'no-repeat', backgroundPosition: 'right 8px center',
        color: m.color, fontWeight: 700, fontSize: lg ? 13 : 11.5, borderRadius: 999, padding: lg ? '7px 28px 7px 14px' : '3px 22px 3px 11px',
        cursor: 'pointer', fontFamily: 'inherit', outline: 'none', maxWidth: '100%' }}>
      {APO_STATUS_ORDER.map(k => <option key={k} value={k} style={{ color: '#1f2430', background: '#fff' }}>{t(APO_STATUS[k].labelKey)}</option>)}
    </select>
  );
}

function ApoLeadDetail() {
  const { route, navigate, rcLeads, genEntryLetter, scoreLeads, showToast, can, saveAiReport } = useStore();
  const cur = (rcLeads || []).find(l => l.id === route.id);
  const [letter, setLetter] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [scoring, setScoring] = React.useState(false);
  const [copied, setCopied] = React.useState(false);

  const back = (
    <button onClick={() => navigate('apo')} 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('apo.backToList')}
    </button>
  );
  if (!cur) return (
    <Page title={t('apo.detailTitle')}>{back}
      <Card><div style={{ padding: '40px 20px', textAlign: 'center', color: '#9aa1ab', fontSize: 13, lineHeight: 1.7 }}>{t('apo.leadNotFound')}<br />{t('apo.leadNotFoundHint')}</div></Card>
    </Page>
  );

  const srcHnavi = cur.source === 'hnavi';
  const srcName = srcHnavi ? '発注ナビ' : 'ReadyCrew';
  const idLabel = srcHnavi ? ('HNV-' + String(cur.matchingId || '').replace(/^hnv/, '')) : ('RDC-' + cur.matchingId);

  const gen = async () => {
    if (busy) return; setBusy(true);
    try {
      const r = await genEntryLetter(cur.id);
      const text = (r && r.letter) || '';
      setLetter(text);
      // 生成成功時は履歴へ保存（リードに紐付け。リロードしても残る・後から調閲/削除できる）
      if (text) saveAiReport({ type: 'entry_letter', title: cur.company || t('apo.companyUnknown'), content: text, refId: cur.id, meta: { leadId: cur.id, title: cur.title || '' } });
    }
    catch (e) { showToast(t('apo.toast.genFailed', { msg: e.message }), 'x'); }
    setBusy(false);
  };
  const reScore = async () => {
    if (scoring) return; setScoring(true);
    try { await scoreLeads([cur.id]); showToast(t('apo.toast.rescored')); }
    catch (e) { showToast(t('apo.toast.scoreFailed', { msg: e.message }), 'x'); }
    setScoring(false);
  };
  const copy = () => { navigator.clipboard.writeText(letter).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }).catch(() => showToast(t('apo.toast.copyFailed'), 'x')); };

  const Row = ({ label, children }) => (
    <div style={{ display: 'flex', gap: 14, padding: '12px 0', borderBottom: '1px solid #f4f5f7' }}>
      <div style={{ width: 96, flex: '0 0 auto', fontSize: 12, color: '#9aa1ab', fontWeight: 600 }}>{label}</div>
      <div style={{ flex: 1, minWidth: 0, fontSize: 13.5, color: '#2b2f38', lineHeight: 1.75, whiteSpace: 'pre-wrap' }}>{children || '—'}</div>
    </div>
  );

  const right = (
    <div style={{ display: 'flex', gap: 9 }}>
      <a href={cur.sourceUrl} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}><Button variant="default" icon="link">{srcHnavi ? window.t("extra.apo.openHnavi") : t('apo.openInReadyCrew')}</Button></a>
      {can('apoOps') && <Button variant="primary" icon="spark" onClick={gen} disabled={busy}>{busy ? t('apo.generating') : t('apo.genEntryLetter')}</Button>}
    </div>
  );

  return (
    <Page title={t('apo.detailTitle')} right={right}>
      {back}
      {/* ヘッダ */}
      <Card>
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, flexWrap: 'wrap' }}>
          <span style={{ fontSize: 19, fontWeight: 700, color: '#1f2430' }}>{cur.company || (cur.source === 'hnavi' ? (cur.title || window.t("extra.apo.hnaviCase")) : t('apo.companyUnknown'))}</span>
          {(() => { const m = rcLeadStatusMeta(cur); return <span style={{ fontSize: 11, fontWeight: 700, color: m.color, background: m.bg, padding: '3px 10px', borderRadius: 999 }}>{m.label}</span>; })()}
          {cur.viewed === false && <span title={window.t("extra.apo.unopenedHint")} style={{ fontSize: 11, fontWeight: 700, color: '#b91c1c', background: '#fdecec', padding: '3px 10px', borderRadius: 999 }}>{window.t("extra.apo.unopened")}</span>}
          {cur.prefecture && <span style={{ fontSize: 11.5, fontWeight: 600, color: '#1d4ed8', background: '#e8f0fe', padding: '3px 9px', borderRadius: 999 }}>{cur.prefecture}</span>}
          {cur.category && <span style={{ fontSize: 12, color: '#7b828d' }}>{cur.category}</span>}
        </div>
        {(cur.title || cur.desc) && <div style={{ fontSize: 13.5, color: '#3b414b', marginTop: 8, lineHeight: 1.6 }}>{cur.title || cur.desc}</div>}
        <div style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: '#aab0ba', marginTop: 7 }}>{idLabel} · {t('apo.registeredPrefix')} {cur.registeredAt ? fmtDateY(cur.registeredAt) : '—'}</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 13, paddingTop: 13, borderTop: '1px solid #f1f2f4', flexWrap: 'wrap' }}>
          <span style={{ fontSize: 12.5, fontWeight: 700, color: '#5b626d' }}>{t('apo.entryDecision')}</span>
          <ApoStatusSelect lead={cur} size="lg" />
          <span style={{ fontSize: 11.5, color: '#aab0ba' }}>{t('apo.entryDecisionHint')}</span>
        </div>
      </Card>

      {/* AI 成件機率＋予算 */}
      <Card style={{ marginTop: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <div style={{ width: 62, height: 62, borderRadius: '50%', flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 17, fontWeight: 800, color: '#fff', background: scoreColor(cur.aiScore) }}>
            {cur.aiScore == null ? '—' : cur.aiScore + '%'}
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: '#3b414b', display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              {t('apo.winProbability')}{cur.aiScore != null ? (cur.aiRecommend ? t('apo.recommendSuffix') : t('apo.watchSuffix')) : ''}
              {cur.aiBudget && <span style={{ fontSize: 11.5, fontWeight: 700, color: '#15803d', background: '#e3f5e9', padding: '2px 9px', borderRadius: 999 }}>{t('apo.estBudget', { budget: cur.aiBudget })}</span>}
            </div>
            <div style={{ fontSize: 12.5, color: '#7b828d', marginTop: 4, lineHeight: 1.65 }}>{cur.aiReason || (cur.aiScore == null ? t('apo.notEvaluatedHint') : '')}</div>
          </div>
          <Button variant="default" size="sm" icon="refresh" onClick={reScore} disabled={scoring}>{scoring ? t('apo.scoring') : t('apo.rescore')}</Button>
        </div>
      </Card>

      {/* 詳細情報（ReadyCrew 全項目） */}
      <Card style={{ marginTop: 14 }}>
        <Row label={t('apo.row.company')}>{cur.company}</Row>
        <Row label={t('apo.row.title')}>{cur.title}</Row>
        <Row label={t('apo.row.category')}>{cur.category}</Row>
        <Row label={t('apo.row.desc')}>{cur.desc}</Row>
        <Row label={t('apo.row.detail')}>{cur.detail || t('apo.detailNotFetched')}</Row>
        <Row label={t('apo.row.location')}>{cur.prefecture ? cur.prefecture + (cur.station ? t('apo.nearestStation', { station: cur.station }) : '') : '—'}</Row>
        <Row label={t('apo.row.companySize')}>{[cur.employeeCount != null ? t('apo.employees', { count: cur.employeeCount }) : '', cur.salesAmount ? t('apo.sales', { amount: fmtYen(cur.salesAmount) }) : '', cur.capitalAmount ? t('apo.capital', { amount: fmtYen(cur.capitalAmount) }) : ''].filter(Boolean).join('　') || '—'}</Row>
        <Row label={window.t("extra.apo.industry")}>{[cur.industry, cur.industrySub, cur.industryDetail].filter(Boolean).join(' ／ ')}</Row>
        <Row label={window.t("extra.apo.companyDates")}>{[cur.foundedOn ? fmtDateY(cur.foundedOn) + ' 設立' : (cur.foundedYear ? cur.foundedYear + '年 設立' : ''), cur.fiscalMonth ? '決算 ' + cur.fiscalMonth : ''].filter(Boolean).join('　／　')}</Row>
        <Row label={t('apo.row.companyOverview')}>{cur.companyOverview}</Row>
        <Row label={t('apo.row.appointAt')}>{cur.appointAt ? `${fmtDateY(cur.appointAt)} ${fmtTime(cur.appointAt)}` : t('apo.undecided')}</Row>
        <Row label={t('apo.row.registeredAt')}>{cur.registeredAt ? fmtDateY(cur.registeredAt) : '—'}</Row>
        {Array.isArray(cur.contacts) && cur.contacts.length > 0
          ? <Row label={window.t("extra.apo.contact")}>
              {cur.contacts.map((p, i) => (
                <div key={i} style={{ marginTop: i ? 10 : 0 }}>
                  <div style={{ fontWeight: 700 }}>{[p.name, p.title].filter(Boolean).join('　') || window.t("extra.apo.noName")}{p.department ? '（' + p.department + '）' : ''}</div>
                  {(p.email || p.tel || p.mobile) && <div style={{ fontSize: 12.5, color: '#5b626d' }}>{[p.email, p.tel, p.mobile].filter(Boolean).join('　／　')}</div>}
                  {Array.isArray(p.roleTags) && p.roleTags.length > 0 && <div style={{ fontSize: 11.5, color: '#7b828d' }}>{p.roleTags.join('・')}</div>}
                  {p.memo && <div style={{ fontSize: 12.5, color: '#5b626d', marginTop: 3 }}>{p.memo}</div>}
                </div>
              ))}
            </Row>
          : (cur.contact || cur.email || cur.tel) ? <Row label={window.t("extra.apo.contactDetails")}>{[cur.contact, cur.email, cur.tel].filter(Boolean).join('　／　')}</Row> : null}
        {Array.isArray(cur.meetingLinks) && cur.meetingLinks.length > 0 && (
          <Row label={window.t("calendar.link.meetingLink")}>{cur.meetingLinks.map((l, i) => <div key={i}><a href={l.url} target="_blank" rel="noreferrer" style={{ color: '#4a5af0', wordBreak: 'break-all' }}>{l.url}</a></div>)}</Row>
        )}
        <div style={{ display: 'flex', gap: 14, padding: '12px 0' }}>
          <div style={{ width: 96, flex: '0 0 auto', fontSize: 12, color: '#9aa1ab', fontWeight: 600 }}>{t('apo.row.companySite')}</div>
          <div style={{ flex: 1, minWidth: 0, fontSize: 13 }}>{cur.companyUrl ? <a href={cur.companyUrl} target="_blank" rel="noreferrer" style={{ color: '#4a5af0', wordBreak: 'break-all' }}>{cur.companyUrl}</a> : '—'}</div>
        </div>
        <div style={{ display: 'flex', gap: 14, paddingTop: 12 }}>
          <div style={{ width: 96, flex: '0 0 auto', fontSize: 12, color: '#9aa1ab', fontWeight: 600 }}>{srcName}</div>
          <a href={cur.sourceUrl} target="_blank" rel="noreferrer" style={{ fontSize: 13, color: '#4a5af0', wordBreak: 'break-all' }}>{cur.sourceUrl}</a>
        </div>
      </Card>

      {/* エントリ提案文 */}
      <Card style={{ marginTop: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
          <span style={{ fontSize: 13, fontWeight: 700, color: '#3b414b' }}>{t('apo.entryLetterTitle')}</span>
          {letter && <Button variant="default" size="sm" icon={copied ? 'check' : 'attach'} onClick={copy}>{copied ? t('apo.copied') : t('apo.copy')}</Button>}
        </div>
        {letter ? (
          <>
            <textarea value={letter} onChange={(e) => setLetter(e.target.value)} rows={12} style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.75 }} />
            <div style={{ fontSize: 11.5, color: '#aab0ba', marginTop: 6 }}>{t('apo.editBeforePaste')}</div>
          </>
        ) : (
          <div style={{ padding: '22px 18px', textAlign: 'center', color: '#9aa1ab', fontSize: 12.5, lineHeight: 1.7, background: '#fafbfc', borderRadius: 10, border: '1px dashed #e2e5ea' }}>
            {t('apo.entryLetterEmpty')}
          </div>
        )}
        {/* このリードで生成したエントリ文の履歴（調閲・削除） */}
        <AiReportHistory type="entry_letter" refId={cur.id} title={t('apo.entryLetterHistory')} />
      </Card>
    </Page>
  );
}

function ApoScreen() {
  const { rcLeads, cases, currentUser, scoreLeads, reloadLeads, navigate, showToast, can } = useStore();
  const isMobile = useIsMobile();
  const isAdmin = can('apoOps'); // アポ操作（取込・AI評価）はマネジャー＋
  const [q, setQ] = React.useState('');
  const [status, setStatus] = React.useState('all');
  const [grp, setGrp] = React.useState('all'); // 募集状況（進行中／終了）。既定は全件＝ReadyCrew の全アポを台帳として見せる
  const [apoSt, setApoSt] = React.useState('all');
  const [dateFrom, setDateFrom] = React.useState('');
  const [dateTo, setDateTo] = React.useState('');
  const [sortKey, setSortKey] = React.useState('registered');
  const [sortDir, setSortDir] = React.useState('desc');
  const [openImport, setOpenImport] = React.useState(false);
  const [openHnavi, setOpenHnavi] = React.useState(false);
  // ページ番号はURL(?page=N)に反映＝詳細から戻っても同じページに戻る（絞り込み変更時は1ページ目へ）
  const [page, setPage] = useUrlPage([q, status, grp, apoSt, dateFrom, dateTo, sortKey, sortDir]);
  const [scoring, setScoring] = React.useState(false);
  const [scoreProg, setScoreProg] = React.useState(null); // 一括採点の進捗 { done, remaining }
  const [reloading, setReloading] = React.useState(false);
  const PER = 30;
  // 未採点が残っている限り自動でループ（サーバは1回40件・remaining付き）。大量リードでも1クリックで全採点。
  const runScore = async () => {
    if (scoring) return; setScoring(true); setScoreProg(null);
    let done = 0, batches = 0, lastRemaining = 0;
    try {
      for (;;) {
        const r = await scoreLeads();
        done += (r.scored || 0); lastRemaining = r.remaining || 0; batches++;
        setScoreProg({ done, remaining: lastRemaining });
        if (!r.scored || !r.remaining) break;      // 進まない/残ゼロで終了
        if (batches >= 60) break;                  // 安全弁（最大 ~2400件/回）
        await new Promise(res => setTimeout(res, 300));
      }
      showToast(lastRemaining ? t('apo.toast.scoredRemaining', { scored: done, remaining: lastRemaining }) : (done ? t('apo.toast.scoredDone', { scored: done }) : '未採点のリードはありませんでした'));
    }
    catch (e) { showToast(t('apo.toast.scoreBatchFailed', { msg: e.message }), 'x'); }
    setScoring(false); setScoreProg(null);
  };
  const reload = async () => {
    if (reloading) return; setReloading(true);
    try { const r = await reloadLeads(); showToast(r.added ? t('apo.toast.reloadAdded', { added: r.added, total: r.total }) : t('apo.toast.reloadNone', { total: r.total })); }
    catch (e) { showToast(t('apo.toast.reloadFailed', { msg: e.message }), 'x'); }
    setReloading(false);
  };

  // 案件化済み（案件管理に表示）のリードはアポ管理から除外＝二重表示を防ぐ。
  // 案件化＝同一 matchingId の「正規の case」（id='k'+matchingId かつ リード状態でない）が存在すること。
  // ※ rcStatus が未回答(1)等のままの case は案件化と見なさない＝そのリードはアポ管理に残す（案件一覧には出さない）。
  const caseIds = rcCaseIdSet(cases);
  // ★全リード表示：商談化したリードも隠さず残す（商談化はステータス欄で「エントリー済み」と表示）。
  //   以前は商談化を除外して二重表示を防いでいたが、アポ管理を「全リードの台帳＋エントリー追跡」にする方針へ変更。
  const apoPool = (rcLeads || []);

  // 取込済みリードの rcStatus 一覧（動的・アポ管理に残っているもののみ）
  // 募集状況の選択肢は statusForClient ベース（未回答／受付終了 等を正しく出し分ける）
  const statusSeen = new Map();
  apoPool.forEach(l => { const k = rcLeadStatusKey(l); if (!statusSeen.has(k)) statusSeen.set(k, rcLeadStatusLabel(l)); });
  const statusVals = Array.from(statusSeen.keys()).sort();
  const statusOpts = [{ value: 'all', label: t('cases.all') }, ...statusVals.map(k => ({ value: k, label: statusSeen.get(k) }))];
  const grpOpts = [{ value: 'all', label: t('cases.all') }, { value: '進行中', get label(){return window.t("cust.col.active");} }, { value: '終了', get label(){return window.t("an.rangeModal.end");} }];

  const dkey = (l) => (l.registeredAt || l.importedAt || '').slice(0, 10); // YYYY-MM-DD
  const apoStOpts = [{ value: 'all', label: t('cases.all') }, ...APO_STATUS_ORDER.map(k => ({ value: k, label: t(APO_STATUS[k].labelKey) }))];
  let rows = apoPool.filter(l => {
    if (status !== 'all' && rcLeadStatusKey(l) !== status) return false;
    if (grp !== 'all' && (RC_GROUPS[rcLeadStatusMeta(l).group] || '進行中') !== grp) return false;
    // 商談化リードは実効ステータス＝applied（エントリー済み）として絞り込む
    if (apoSt !== 'all') { const effSt = rcLeadEntered(l, caseIds) ? 'applied' : (APO_STATUS[l.apoStatus] ? l.apoStatus : 'none'); if (effSt !== apoSt) return false; }
    if (q) { const hay = ((l.company || '') + (l.title || '') + (l.category || '') + (l.desc || '') + (l.prefecture || '')).toLowerCase(); if (!hay.includes(q.toLowerCase())) return false; }
    const d = dkey(l);
    if ((dateFrom || dateTo) && !d) return false;
    if (dateFrom && d < dateFrom) return false;
    if (dateTo && d > dateTo) return false;
    return true;
  });
  const cmp = {
    registered: (a, b) => (a.registeredAt || a.importedAt || '').localeCompare(b.registeredAt || b.importedAt || ''),
    score: (a, b) => (a.aiScore == null ? -1 : a.aiScore) - (b.aiScore == null ? -1 : b.aiScore),
    company: (a, b) => (a.company || '').localeCompare(b.company || '', 'ja'),
    prefecture: (a, b) => (a.prefecture || '').localeCompare(b.prefecture || '', 'ja'),
    title: (a, b) => (a.title || a.desc || '').localeCompare(b.title || b.desc || '', 'ja'),
  };
  rows = rows.slice().sort((a, b) => { const r = (cmp[sortKey] || cmp.registered)(a, b); return sortDir === 'desc' ? -r : r; });

  const totalPages = Math.max(1, Math.ceil(rows.length / PER));
  const safePage = Math.min(page, totalPages);
  syncUrlPage(safePage);
  const pageRows = rows.slice((safePage - 1) * PER, safePage * PER);

  const headerSort = (k) => { if (sortKey === k) setSortDir(d => d === 'desc' ? 'asc' : 'desc'); else { setSortKey(k); setSortDir(k === 'company' || k === 'prefecture' || k === 'title' ? 'asc' : 'desc'); } };
  const SortH = ({ label, k, alignRight }) => {
    const a = sortKey === k;
    return (
      <div onClick={() => headerSort(k)} className="row-hover-plain"
        style={{ display: 'inline-flex', alignItems: 'center', gap: 3, cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap', color: a ? '#4a5af0' : '#9aa1ab', justifySelf: alignRight ? 'end' : 'start' }}>
        {label}<Icon name="chevronDown" size={12} stroke={2.4} style={{ opacity: a ? 1 : 0.3, transform: a && sortDir === 'asc' ? 'rotate(180deg)' : 'none' }} />
      </div>
    );
  };

  const right = isAdmin ? (
    <div style={{ display: 'flex', gap: isMobile ? 6 : 9, flexWrap: 'wrap', justifyContent: isMobile ? 'flex-start' : 'flex-end' }}>
      <Button variant="default" size={isMobile ? 'sm' : undefined} icon="refresh" onClick={reload} disabled={reloading}>{reloading ? t('apo.reloading') : t('apo.reloadLatest')}</Button>
      <Button variant="default" size={isMobile ? 'sm' : undefined} icon="spark" onClick={runScore} disabled={scoring}>{scoring ? (scoreProg ? (""+window.t("extra.apo.scoring")+" ") + scoreProg.done + window.t("unit.count") + (scoreProg.remaining ? window.t("extra.common.remaining") + scoreProg.remaining + '）' : '') : t('apo.scoringBatch')) : t('apo.scoreBatch')}</Button>
      <Button variant="default" size={isMobile ? 'sm' : undefined} icon="download" onClick={() => setOpenImport(true)}>{t('apo.readyCrewImport')}</Button>
      <Button variant="default" size={isMobile ? 'sm' : undefined} icon="download" onClick={() => setOpenHnavi(true)}>{t('cases.hnaviImport')}</Button>
    </div>
  ) : null;
  const dInput = { border: '1px solid #e2e5ea', borderRadius: 8, padding: '6px 9px', fontSize: 12.5, fontFamily: 'inherit', color: '#3b414b', background: '#fff', outline: 'none' };

  return (
    <Page title={t('nav.apo')} right={right}>
      <div style={{ fontSize: 12.5, color: '#7b828d', marginBottom: 14, lineHeight: 1.6, padding: '11px 14px', background: '#f6f7f9', borderRadius: 10 }}>
        <Icon name="inbox" size={14} stroke={2} style={{ color: '#4a5af0', verticalAlign: '-2px', marginRight: 6 }} />
        {t('apo.intro')}
      </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: 240 }}>
          <Icon name="search" size={15} stroke={2} style={{ color: '#9aa1ab' }} />
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t('apo.searchPlaceholder')}
            style={{ border: 'none', outline: 'none', fontSize: 13, width: '100%', fontFamily: 'inherit', background: 'transparent' }} />
        </div>
        <FilterPill label={window.t("extra.apo.recruitment")} value={grp} options={grpOpts} onChange={setGrp} icon="filter" />
        {statusVals.length > 1 && <FilterPill label={t('apo.statusFilter')} value={status} options={statusOpts} onChange={setStatus} icon="filter" />}
        <FilterPill label={t('apo.col.status')} value={apoSt} options={apoStOpts} onChange={setApoSt} icon="check" />
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12.5, color: '#7b828d' }}>
          <Icon name="calendar" size={14} stroke={2} style={{ color: '#9aa1ab' }} />{t('apo.registeredDate')}
          <input type="date" value={dateFrom} onChange={(e) => setDateFrom(e.target.value)} style={dInput} />
          <span style={{ color: '#c4c9d0' }}>〜</span>
          <input type="date" value={dateTo} onChange={(e) => setDateTo(e.target.value)} style={dInput} />
          {(dateFrom || dateTo) && <button onClick={() => { setDateFrom(''); setDateTo(''); }} style={{ border: 'none', background: 'transparent', color: '#9aa1ab', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit' }}>{t('apo.clear')}</button>}
        </div>
        <div style={{ marginLeft: 'auto', fontSize: 12.5, color: '#9aa1ab' }}>{t('apo.countLeads', { count: rows.length })}</div>
      </div>

      <Card pad={0}>
        {/* ヘッダー行（カラム＋ソート）＝PCのみ。スマホはカード表示 */}
        {!isMobile && (
        <div style={{ ...APO_GRID, padding: '11px 18px', borderBottom: '1px solid #eef0f3', background: '#fafbfc', fontSize: 11.5, fontWeight: 700 }}>
          <SortH label={t('apo.col.registered')} k="registered" />
          <SortH label={t('apo.col.location')} k="prefecture" />
          <div style={{ color: '#9aa1ab' }}>{t('apo.col.budgetAi')}</div>
          <SortH label={t('apo.col.company')} k="company" />
          <SortH label={t('apo.col.title')} k="title" />
          <SortH label={t('apo.col.ai')} k="score" alignRight />
          <div style={{ color: '#9aa1ab' }}>{window.t("extra.common.source")}</div>
          <div style={{ color: '#9aa1ab' }}>{t('apo.col.status')}</div>
          <div />
        </div>
        )}
        {isMobile && pageRows.map((l, i) => {
          const entered = rcLeadBecameCase(l, caseIds) || hnaviEntered(l);
          const via = apoViaMeta(l);
          return (
            <div key={l.id} className="row-hover" onClick={() => navigate('apoLead', l.id)}
              style={{ padding: '13px 15px', cursor: 'pointer', borderBottom: i === pageRows.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
              <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.company || (l.source === 'hnavi' ? (l.title || window.t("extra.apo.hnaviCase")) : t('apo.companyUnknown'))}</div>
                  <div style={{ fontSize: 12, color: '#7b828d', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{(l.source === 'hnavi' && !l.company) ? window.t("extra.apo.hiddenCompany") : (l.title || l.desc || '—')}</div>
                </div>
                {l.aiScore != null
                  ? <span style={{ flex: '0 0 auto', fontSize: 11.5, fontWeight: 700, color: scoreColor(l.aiScore), background: scoreColor(l.aiScore) + '1f', padding: '3px 9px', borderRadius: 999, whiteSpace: 'nowrap' }}>{l.aiRecommend ? '★ ' : ''}{l.aiScore}%</span>
                  : <span style={{ flex: '0 0 auto', fontSize: 10.5, color: '#b4bac3', border: '1px solid #eceef1', padding: '2px 8px', borderRadius: 999 }}>{t('apo.notEvaluated')}</span>}
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 8, flexWrap: 'wrap' }}>
                <span title={via.label} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10, fontWeight: 700, color: via.color, background: via.bg, padding: '2px 7px', borderRadius: 999 }}>
                  <Icon name={via.icon} size={9} stroke={2.2} />{via.label}
                </span>
                {(() => { const m = rcLeadStatusMeta(l); return <span style={{ fontSize: 10, fontWeight: 700, color: m.color, background: m.bg, padding: '2px 7px', borderRadius: 999 }}>{m.label}</span>; })()}
                {l.viewed === false && <span style={{ fontSize: 10, fontWeight: 700, color: '#b91c1c', background: '#fdecec', padding: '2px 7px', borderRadius: 999 }}>{window.t("extra.apo.unopened")}</span>}
                {l.aiBudget && <span style={{ fontSize: 10.5, fontWeight: 600, color: '#15803d', background: '#e3f5e9', padding: '2px 8px', borderRadius: 999 }}>{l.aiBudget}</span>}
                {l.prefecture && <span style={{ fontSize: 10.5, color: '#7b828d' }}><Icon name="mapPin" size={10} stroke={2} style={{ verticalAlign: '-1px' }} /> {l.prefecture}</span>}
                {l.registeredAt && <span style={{ fontSize: 10.5, color: '#aab0ba', marginLeft: 'auto' }}>{fmtDateY(l.registeredAt)}</span>}
                {entered && <span style={{ fontSize: 10, fontWeight: 700, color: '#15803d', background: '#e3f5e9', padding: '2px 8px', borderRadius: 999 }}>{window.t("extra.apo.entered")}</span>}
              </div>
            </div>
          );
        })}
        {!isMobile && pageRows.map((l, i) => (
          <div key={l.id} className="row-hover" onClick={() => navigate('apoLead', l.id)}
            style={{ ...APO_GRID, padding: '12px 18px', cursor: 'pointer', borderBottom: i === pageRows.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
            <div style={{ fontSize: 12, color: '#5b626d', whiteSpace: 'nowrap' }}>{l.registeredAt ? fmtDateY(l.registeredAt) : '—'}</div>
            <div style={{ fontSize: 12.5, color: '#3b414b', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.prefecture || '—'}</div>
            <div style={{ fontSize: 11.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {l.aiBudget ? <span style={{ fontWeight: 600, color: '#15803d', background: '#e3f5e9', padding: '2px 8px', borderRadius: 999 }}>{l.aiBudget}</span> : <span style={{ color: '#c4c9d0' }}>{l.aiScore == null ? t('apo.notEvaluated') : '—'}</span>}
            </div>
            <div style={{ fontSize: 13, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.company || (l.source === 'hnavi' ? (l.title || window.t("extra.apo.hnaviCase")) : t('apo.companyUnknown'))}</div>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 13, color: '#2b2f38', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{(l.source === 'hnavi' && !l.company) ? <span style={{ color: '#c2410c' }}>{window.t("extra.apo.hiddenCompany")}</span> : (l.title || l.desc || '—')}</div>
              <div style={{ fontSize: 11, color: '#aab0ba', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', marginTop: 2, display: 'flex', alignItems: 'center', gap: 6 }}>
                {(() => { const m = rcLeadStatusMeta(l); return <span style={{ flex: '0 0 auto', fontWeight: 700, color: m.color, background: m.bg, padding: '1px 7px', borderRadius: 999 }}>{m.label}</span>; })()}
                {l.viewed === false && <span title={window.t("extra.apo.rcUnopened")} style={{ flex: '0 0 auto', fontWeight: 700, color: '#b91c1c', background: '#fdecec', padding: '1px 7px', borderRadius: 999 }}>{window.t("extra.apo.unopened")}</span>}
                <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{[l.category, l.desc].filter(Boolean).join(' · ') || '—'}</span>
              </div>
            </div>
            <div style={{ justifySelf: 'end' }}>
              {l.aiScore != null
                ? <span style={{ fontSize: 11.5, fontWeight: 700, color: scoreColor(l.aiScore), background: scoreColor(l.aiScore) + '1f', padding: '3px 9px', borderRadius: 999, whiteSpace: 'nowrap' }}>{l.aiRecommend ? '★ ' : ''}{l.aiScore}%</span>
                : <span style={{ fontSize: 11, color: '#c4c9d0' }}>—</span>}
            </div>
            <div style={{ minWidth: 0 }}>
              {(() => { const v = apoViaMeta(l); return (
                <span title={v.label} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10.5, fontWeight: 700, color: v.color, background: v.bg, padding: '2px 8px', borderRadius: 999, whiteSpace: 'nowrap', maxWidth: '100%' }}>
                  <Icon name={v.icon} size={10} stroke={2.2} style={{ flex: '0 0 auto' }} /><span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{v.label}</span>
                </span>
              ); })()}
            </div>
            <div onClick={(e) => e.stopPropagation()} style={{ minWidth: 0 }}>
              {(rcLeadBecameCase(l, caseIds) || hnaviEntered(l))
                ? <span title={rcLeadBecameCase(l, caseIds) ? window.t("extra.apo.autoEntered") : window.t("extra.apo.hnaviEntered")} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, fontWeight: 700, color: '#15803d', background: '#e3f5e9', padding: '3px 10px', borderRadius: 999, whiteSpace: 'nowrap' }}><Icon name="check" size={11} stroke={2.6} />{window.t("extra.apo.entered")}</span>
                : l.source === 'hnavi'
                  ? <span title={window.t("extra.apo.hnaviNotEntered")} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11, fontWeight: 700, color: '#7b828d', background: '#f0f1f4', padding: '3px 10px', borderRadius: 999, whiteSpace: 'nowrap' }}>{window.t("extra.apo.recruiting")}</span>
                  : <ApoStatusSelect lead={l} />}
            </div>
            <Icon name="chevronRight" size={16} stroke={2} style={{ color: '#c4c9d0', justifySelf: 'end' }} />
          </div>
        ))}
        {rows.length === 0 && (
          <div style={{ padding: '46px 20px', textAlign: 'center', color: '#9aa1ab', fontSize: 13, lineHeight: 1.7 }}>
            {(rcLeads || []).length === 0 ? <>{t('apo.empty.noLeads')}<br />{isAdmin ? t('apo.empty.adminHint') : t('apo.empty.memberHint')}</> : t('apo.empty.noMatch')}
          </div>
        )}
      </Card>

      {totalPages > 1 && (() => {
        const nums = [];
        for (let i = 1; i <= totalPages; i++) {
          if (i === 1 || i === totalPages || (i >= safePage - 1 && i <= safePage + 1)) nums.push(i);
          else if (nums[nums.length - 1] !== '…') nums.push('…');
        }
        const pill = { minWidth: 34, height: 34, padding: '0 9px', borderRadius: 8, border: '1px solid #e2e5ea', background: '#fff', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600, color: '#3b414b', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 4 };
        const nav = (dir) => { const dis = dir < 0 ? safePage === 1 : safePage === totalPages; return (
          <button onClick={() => setPage(p => Math.min(totalPages, Math.max(1, p + dir)))} disabled={dis} style={{ ...pill, opacity: dis ? 0.4 : 1, cursor: dis ? 'not-allowed' : 'pointer' }}>
            {dir < 0 && <Icon name="chevronLeft" size={14} stroke={2.2} />}{dir < 0 ? t('btn.prev') : t('btn.next')}{dir > 0 && <Icon name="chevronRight" size={14} stroke={2.2} />}
          </button>); };
        return (
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, marginTop: 16, flexWrap: 'wrap' }}>
            {nav(-1)}
            {nums.map((n, i) => n === '…' ? <span key={'e' + i} style={{ minWidth: 22, textAlign: 'center', color: '#b4bac3' }}>…</span>
              : <button key={n} onClick={() => setPage(n)} style={{ ...pill, ...(n === safePage ? { background: '#4a5af0', borderColor: '#4a5af0', color: '#fff' } : {}) }}>{n}</button>)}
            {nav(1)}
          </div>
        );
      })()}

      {openImport && <RCImportModal onClose={() => setOpenImport(false)} />}
      {openHnavi && <HnaviImportModal onClose={() => setOpenHnavi(false)} />}
    </Page>
  );
}

Object.assign(window, { ApoScreen, ApoLeadDetail });
