/* ============================================================
   案件詳細 — メールタブ（MailTab / OrdererReview / FeedbackRow）。case-detail.jsx から分離（依存はグローバル＝render時解決）。
   ============================================================ */
function NoteEditor({ caseData, editable, onSave }) {
  const [val, setVal] = React.useState(caseData.note || '');
  const [dirty, setDirty] = React.useState(false);
  React.useEffect(() => { setVal(caseData.note || ''); setDirty(false); }, [caseData.id]);
  if (!editable) {
    return <div style={{ fontSize: 13, color: caseData.note ? '#3b414b' : '#b4bac3', lineHeight: 1.7, whiteSpace: 'pre-wrap', background: '#fafafb', border: '1px solid #f0f1f4', borderRadius: 10, padding: '14px 16px' }}>{caseData.note || t('cd.noMemo')}</div>;
  }
  return (
    <div>
      <textarea value={val} onChange={(e) => { setVal(e.target.value); setDirty(true); }} placeholder={t('cd.notePlaceholder')} rows={3}
        style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.6 }} />
      {dirty && (
        <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
          <Button variant="primary" size="sm" icon="check" onClick={() => { onSave(val); setDirty(false); }}>{t('btn.save')}</Button>
          <Button variant="subtle" size="sm" onClick={() => { setVal(caseData.note || ''); setDirty(false); }}>{t('cd.cancelShort')}</Button>
        </div>
      )}
    </div>
  );
}

/* メール履歴タブ（Gmail）。顧客単位の往来メールを時系列表示＋状態表示 */
/* やり取りの要約（AI）：メール一覧の最上部に「これまでの流れ」＋「最新状況」＋ボール所在を表示。
   結果は case.mailDigest にキャッシュし、メール通数が変わったら自動で作り直す（開くたびのAI呼び出しはしない）。 */
function MailDigestCard({ caseData, mails }) {
  const { patchCase } = useStore();
  const D = window.APP_DATA;
  const cust = D.customer(caseData.customerId) || {};
  const dig = caseData.mailDigest || null;
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const gen = async () => {
    if (busy) return; setBusy(true); setErr('');
    try {
      const payload = {
        company: cust.company || cust.shortName || '', caseTitle: caseData.title || '',
        // 新しい順で保持している一覧を古い順に直して送る（AIに時系列で読ませる）
        mails: mails.slice(0, 12).slice().reverse().map(m => ({ dir: m.direction, at: String(m.date || '').slice(0, 16), subject: m.subject || '', body: String(m.body || m.snippet || '').slice(0, 1200) })),
      };
      const r = await API.mailDigest(payload);
      if (r && (r.flow || r.latest)) patchCase(caseData.id, { mailDigest: { flow: r.flow || '', latest: r.latest || '', ball: r.ball || null, n: mails.length, at: String(new Date().toISOString()).slice(0, 16).replace('T', ' ') } });
      else setErr(t('cd.maildig.failed'));
    } catch (e) { setErr(/404/.test(String(e && e.message)) ? t('cd.maildig.needServer') : ((e && e.message) || t('cd.maildig.failed'))); }
    setBusy(false);
  };
  // 通数が変わった時だけ自動更新（失敗時は自動リトライしない＝再生成ボタンで手動）
  const triedRef = React.useRef(0);
  React.useEffect(() => {
    if (!mails.length) return;
    if (dig && dig.n === mails.length) return;
    if (triedRef.current === mails.length) return;
    triedRef.current = mails.length;
    gen();
  }, [mails.length]);
  if (!mails.length) return null;
  const ball = dig && dig.ball;
  const ballMeta = ball === 'them' ? { txt: t('cd.maildig.ballThem'), c: '#2e9e6b', b: '#eef6f0' } : ball === 'us' ? { txt: t('cd.maildig.ballUs'), c: '#d97706', b: '#fef3e2' } : null;
  return (
    <div style={{ border: '1px solid #e6e4fb', background: '#fbfbff', borderRadius: 12, padding: '13px 16px', marginBottom: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        <Icon name="spark" size={14} stroke={2.2} style={{ color: '#4a5af0', flex: '0 0 auto' }} />
        <span style={{ fontSize: 12.5, fontWeight: 700, color: '#3a49d8' }}>{t('cd.maildig.title')}</span>
        {ballMeta && <span style={{ fontSize: 11, fontWeight: 700, color: ballMeta.c, background: ballMeta.b, padding: '2px 9px', borderRadius: 999 }}>{ballMeta.txt}</span>}
        <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
          {dig && dig.at && <span style={{ fontSize: 10.5, color: '#b4bac3' }}>{dig.at.slice(5)}</span>}
          <button onClick={gen} disabled={busy} style={{ ...linkBtn, fontSize: 12, opacity: busy ? 0.55 : 1 }}>{busy ? t('cd.maildig.generating') : t('cd.maildig.regen')}</button>
        </span>
      </div>
      {busy && !dig && <div style={{ fontSize: 12.5, color: '#9aa1ab', marginTop: 8 }}>{t('cd.maildig.generating')}…</div>}
      {err && <div style={{ fontSize: 12, color: '#c0392b', marginTop: 8 }}>{err}</div>}
      {dig && (
        <div style={{ marginTop: 9, display: 'flex', flexDirection: 'column', gap: 7 }}>
          {dig.flow && <div style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.75 }}>{dig.flow}</div>}
          {dig.latest && (
            <div style={{ fontSize: 12.5, color: '#2b2f38', lineHeight: 1.75, background: '#fff', border: '1px solid #eef0fe', borderRadius: 9, padding: '8px 12px' }}>
              <span style={{ fontWeight: 700, color: '#3a49d8', marginRight: 6 }}>{t('cd.maildig.latestLabel')}</span>{dig.latest}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function MailTab({ caseData }) {
  const { emailsOf, syncCustomerGmail, showToast, loginAs, currentUser } = useStore();
  const D = window.APP_DATA;
  const cust = D.customer(caseData.customerId) || {};
  const mails = emailsOf(caseData.customerId);
  const [busy, setBusy] = React.useState(false);
  const connected = !!(window.gmailAutoEnabled && window.gmailAutoEnabled());
  const sync = async () => {
    if (busy) return; setBusy(true);
    try { await syncCustomerGmail(cust); } catch (e) { showToast(e.message || t('cd.toast.syncFailed'), 'x'); }
    setBusy(false);
  };
  const status = mailStatusOf(mails, caseData.customerId);
  const [openId, setOpenId] = React.useState(null);
  const [bodies, setBodies] = React.useState({}); // gid -> 全文 / 'ERR:...'
  const [loadingId, setLoadingId] = React.useState(null);
  const toggle = async (m) => {
    if (openId === m.id) { setOpenId(null); return; }
    setOpenId(m.id);
    // 本文はサーバー保存（同期時に取得）を優先＝別担当者のメールでも閲覧でき、404を回避。
    // 保存本文が無い旧データのみ、フォールバックで現ユーザーの Gmail からオンデマンド取得を試みる。
    if (m.body || bodies[m.gid] != null) return;
    if (window.gmailFetchBody) {
      setLoadingId(m.id);
      try { const b = await window.gmailFetchBody(m.gid); setBodies(prev => ({ ...prev, [m.gid]: b || t('cd.mailBodyEmpty') })); }
      catch (e) { setBodies(prev => ({ ...prev, [m.gid]: 'ERR:' + (e.message || t('cd.mailFetchFailed')) })); }
      setLoadingId(null);
    }
  };
  // メール作成（AI生成＋本人のGmailから送信）
  const [composeOpen, setComposeOpen] = React.useState(false);
  const [to, setTo] = React.useState(cust.email || '');
  const [cc, setCc] = React.useState('');
  const [subject, setSubject] = React.useState('');
  const [bodyText, setBodyText] = React.useState('');
  const [kind, setKind] = React.useState('followup');
  const [instructions, setInstructions] = React.useState(''); // AIへのカスタム指示（プロンプト）
  const [genBusy, setGenBusy] = React.useState(false);
  const [sendBusy, setSendBusy] = React.useState(false);
  const [atts, setAtts] = React.useState([]); // {name, mime, data(base64), size}
  const fileRef = React.useRef(null);
  // 予約送信：日時はシステム慣例どおりローカル(JST)の 'YYYY-MM-DDTHH:mm' 文字列。比較も文字列の辞書順で行う
  const [sendAt, setSendAt] = React.useState('');
  const [schedBusy, setSchedBusy] = React.useState(false);
  const [sched, setSched] = React.useState([]); // この案件の予約送信（他メンバー分も含む＝二重送信の防止）
  React.useEffect(() => { setTo(cust.email || ''); setCc(''); setComposeOpen(false); setSubject(''); setBodyText(''); setAtts([]); setInstructions(''); setSendAt(''); }, [caseData.customerId]);
  const nowStamp = () => { const d = new Date(); const p = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`; };
  const presetAt = (addDays, hh) => { const d = new Date(); d.setDate(d.getDate() + addDays); const p = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(hh)}:00`; };
  const loadSched = React.useCallback(async () => {
    try { const r = await API.scheduledEmails(caseData.id); setSched((r && r.items) || []); }
    catch (_) { /* 一覧が取れなくても作成・送信はできるので黙って無視 */ }
  }, [caseData.id]);
  React.useEffect(() => { loadSched(); }, [loadSched]);
  // サーバーは1分ごとに送信するので、予約が残っている間だけ1分間隔で状態を追う（残っていなければポーリングしない）
  const hasLive = sched.some(s => s.status === 'pending' || s.status === 'sending');
  React.useEffect(() => {
    if (!hasLive) return;
    const h = setInterval(loadSched, 60 * 1000);
    return () => clearInterval(h);
  }, [hasLive, loadSched]);
  const addFiles = (fileList) => {
    const files = Array.from(fileList || []);
    files.forEach(f => {
      if (f.size > 20 * 1024 * 1024) { showToast(t('cd.mail.attTooBig', { name: f.name }), 'x'); return; }
      const r = new FileReader();
      r.onload = () => { const data = String(r.result).replace(/^data:[^;]*;base64,/, ''); setAtts(prev => [...prev, { name: f.name, mime: f.type || 'application/octet-stream', data, size: f.size }]); };
      r.readAsDataURL(f);
    });
  };
  const removeAtt = (i) => setAtts(prev => prev.filter((_, j) => j !== i));
  const fmtSize = (n) => n >= 1048576 ? (n / 1048576).toFixed(1) + 'MB' : Math.max(1, Math.round(n / 1024)) + 'KB';
  const genEmail = async () => {
    if (genBusy) return; setGenBusy(true);
    try { const r = await API.caseEmail(caseData.id, kind, instructions.trim() || undefined); if (r && r.subject) setSubject(r.subject); if (r && r.body) setBodyText(r.body); }
    catch (e) { showToast((e && e.message) || t('cd.mail.genFailed'), 'x'); }
    setGenBusy(false);
  };
  const sendMail = async () => {
    if (sendBusy) return;
    if (!to.trim() || !subject.trim() || !bodyText.trim()) { showToast(t('cd.mail.fillAll'), 'x'); return; }
    if (atts.reduce((s, a) => s + (a.size || 0), 0) > 22 * 1024 * 1024) { showToast(t('cd.mail.attTotalBig'), 'x'); return; }
    if (!window.confirm(t('cd.mail.sendConfirm', { to: to.trim() + (cc.trim() ? `\nCc: ${cc.trim()}` : '') }))) return;
    setSendBusy(true);
    try {
      await API.gmailSend({ caseId: caseData.id, to: to.trim(), cc: cc.trim() || undefined, subject: subject.trim(), body: bodyText, kind, attachments: atts.map(a => ({ name: a.name, mime: a.mime, data: a.data })) });
      const d = await API.bootstrap(); window.hydrateAppData(d); loginAs(currentUser.id);
      showToast(t('cd.mail.sent'));
      setComposeOpen(false); setSubject(''); setBodyText(''); setAtts([]); setCc('');
    } catch (e) { showToast((e && e.message) || t('cd.mail.sendFailed'), 'x'); }
    setSendBusy(false);
  };
  /* 予約送信：内容をサーバーに預け、指定時刻（JST）にサーバーのスケジューラが本人のGmailから送る。
     ブラウザを閉じても送られる（Gmail側の予約ではなく自前保留なので、送信時に履歴・送信進捗も同時に更新される）。 */
  const scheduleMail = async () => {
    if (schedBusy || sendBusy) return;
    if (!to.trim() || !subject.trim() || !bodyText.trim()) { showToast(t('cd.mail.fillAll'), 'x'); return; }
    if (!sendAt) { showToast(t('cd.mail.schedPick'), 'x'); return; }
    if (sendAt <= nowStamp()) { showToast(t('cd.mail.schedPast'), 'x'); return; }
    if (atts.reduce((s, a) => s + (a.size || 0), 0) > 22 * 1024 * 1024) { showToast(t('cd.mail.attTotalBig'), 'x'); return; }
    if (!window.confirm(t('cd.mail.schedConfirm', { to: to.trim() + (cc.trim() ? `\nCc: ${cc.trim()}` : ''), at: fmtDateTime(sendAt) }))) return;
    setSchedBusy(true);
    try {
      await API.gmailSchedule({ caseId: caseData.id, to: to.trim(), cc: cc.trim() || undefined, subject: subject.trim(), body: bodyText, kind, sendAt, attachments: atts.map(a => ({ name: a.name, mime: a.mime, data: a.data })) });
      showToast(t('cd.mail.scheduled', { at: fmtDateTime(sendAt) }));
      setComposeOpen(false); setSubject(''); setBodyText(''); setAtts([]); setCc(''); setSendAt('');
      loadSched();
    } catch (e) { showToast((e && e.message) || t('cd.mail.schedFailed'), 'x'); }
    setSchedBusy(false);
  };
  const cancelSched = async (s) => {
    if (!window.confirm(t(s.status === 'failed' ? 'cd.mail.schedDismissConfirm' : 'cd.mail.schedCancelConfirm', { subject: s.subject || '' }))) return;
    try { await API.cancelScheduledEmail(s.id); showToast(t('cd.mail.schedCancelled')); loadSched(); }
    catch (e) { showToast((e && e.message) || t('cd.mail.schedCancelFailed'), 'x'); loadSched(); }
  };
  // reply-all：返信対象の受信メールの差出人へ返信し、元の宛先(To/Cc)から自分を除いた全員を Cc に載せる
  // メールヘッダのアドレス一覧を解析。表示名にカンマがある "Last, First" <a@x> でも壊れないよう <...> を先に抽出
  const parseAddrs = (s) => {
    s = String(s || '');
    const out = [];
    (s.match(/<[^>]+>/g) || []).forEach(b => out.push(b.slice(1, -1).trim()));           // <...> 内のアドレス
    (s.replace(/"[^"]*"/g, ' ').replace(/<[^>]+>/g, ' ').match(/[^\s,;<>()]+@[^\s,;<>()]+/g) || []).forEach(a => out.push(a.trim())); // 角括弧なしの生アドレス
    const seen = new Set(); const res = [];
    out.forEach(a => { const lc = a.toLowerCase(); if (a && !seen.has(lc)) { seen.add(lc); res.push(a); } });
    return res;
  };
  const applyReplyAll = () => {
    const inbound = mails.find(m => m.direction === 'in'); // 最新の受信メール（mails は日付降順）
    if (!inbound) return; // 受信メールが無ければ既定の宛先のまま
    // 手入力を尊重：To が既定(cust.email)のまま かつ Cc 未入力のときだけ自動設定（手動編集を上書きしない）
    if ((to.trim() && to.trim() !== (cust.email || '').trim()) || cc.trim()) return;
    const selfLc = ((currentUser && currentUser.email) || '').toLowerCase();
    const toAddr = parseAddrs(inbound.from)[0] || cust.email || '';
    const seen = new Set([toAddr.toLowerCase(), selfLc].filter(Boolean));
    const ccList = [];
    parseAddrs(inbound.to).concat(parseAddrs(inbound.cc)).forEach(a => { const lc = a.toLowerCase(); if (lc && !seen.has(lc)) { seen.add(lc); ccList.push(a); } });
    setTo(toAddr); setCc(ccList.join(', '));
  };
  const kindOpts = [
    { v: 'reply', label: t('cd.mail.kind.reply') },
    { v: 'followup', label: t('cd.mail.kind.followup') },
    { v: 'proposal', label: t('cd.mail.kind.proposal') },
    { v: 'thanks', label: t('cd.mail.kind.thanks') },
    { v: 'schedule', label: t('cd.mail.kind.schedule') },
  ];
  const cInput = { width: '100%', border: '1px solid #e2e5ea', borderRadius: 8, padding: '8px 10px', fontSize: 13, fontFamily: 'inherit', color: '#2b2f38', outline: 'none', boxSizing: 'border-box' };
  return (
    <div style={{ padding: 18 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, flexWrap: 'wrap' }}>
        {status && <span style={{ fontSize: 12, fontWeight: 700, color: status.color, background: status.bg, padding: '4px 11px', borderRadius: 999 }}>{status.full}</span>}
        <span style={{ fontSize: 12, color: '#9aa1ab' }}>{cust.email ? t('cd.mailPartner', { email: cust.email }) : t('cd.mailNoEmail')}</span>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
          {cust.email && <Button variant="primary" size="sm" icon="spark" onClick={() => setComposeOpen(o => !o)}>{t('cd.mail.compose')}</Button>}
          <Button variant="default" size="sm" icon="mail" onClick={sync} disabled={busy || !cust.email}>
            {busy ? t('cd.syncing') : (connected ? t('cd.gmailSync') : t('cd.gmailConnectAndSync'))}
          </Button>
        </div>
      </div>
      {/* やり取りの要約（AI）：これまでの流れ＋最新状況。一覧を全部読まなくても状況が掴める */}
      <MailDigestCard caseData={caseData} mails={mails} />
      {/* 予約送信の一覧：これから送るもの＋失敗したもの。送信済みは通常のメール履歴に出るのでここには残さない */}
      {(() => {
        const rows = sched.filter(s => s.status === 'pending' || s.status === 'sending' || s.status === 'failed');
        if (!rows.length) return null;
        return (
          <div style={{ border: '1px solid #e6e4fb', background: '#fbfbff', borderRadius: 12, padding: '10px 14px', marginBottom: 16 }}>
            <div style={{ fontSize: 12, fontWeight: 700, color: '#4a5af0', marginBottom: 6 }}>{t('cd.mail.schedListTitle', { n: rows.length })}</div>
            {rows.map(s => {
              const failed = s.status === 'failed';
              const who = (D.users || []).find(u => u.id === s.userId);
              return (
                <div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '7px 0', borderTop: '1px solid #f0eefc', flexWrap: 'wrap' }}>
                  <span style={{ fontSize: 11.5, fontWeight: 700, flex: '0 0 auto', padding: '2px 9px', borderRadius: 999,
                    color: failed ? '#c2413c' : '#4a5af0', background: failed ? '#fdeceb' : '#eef0fe' }}>
                    {failed ? t('cd.mail.schedTagFailed') : (s.status === 'sending' ? t('cd.mail.schedTagSending') : fmtDateTime(s.sendAt))}
                  </span>
                  <span style={{ fontSize: 12.5, color: '#3b414b', maxWidth: 340, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.subject}</span>
                  <span style={{ fontSize: 11.5, color: '#9aa1ab' }}>
                    → {s.to}{s.attachments && s.attachments.length ? `・${t('cd.mail.schedAttN', { n: s.attachments.length })}` : ''}{who ? `・${who.short || who.name}` : ''}
                  </span>
                  {s.status !== 'sending' && (
                    <button onClick={() => cancelSched(s)}
                      style={{ marginLeft: 'auto', fontSize: 11.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', padding: '2px 10px', borderRadius: 999, border: '1px solid #e2e5ea', background: '#fff', color: '#8a919c' }}>
                      {failed ? t('cd.mail.schedDismiss') : t('cd.mail.schedCancel')}
                    </button>
                  )}
                  {failed && s.error && <div style={{ flexBasis: '100%', fontSize: 11.5, color: '#c2413c', lineHeight: 1.6 }}>{s.error}</div>}
                </div>
              );
            })}
          </div>
        );
      })()}
      {composeOpen && cust.email && (
        <div style={{ border: '1px solid #e6e4fb', background: '#fbfbff', borderRadius: 12, padding: 14, marginBottom: 16 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
            <select value={kind} onChange={(e) => { const k = e.target.value; setKind(k); if (k === 'reply') applyReplyAll(); }} style={{ ...cInput, width: 'auto', cursor: 'pointer' }}>
              {kindOpts.map(o => <option key={o.v} value={o.v}>{o.label}</option>)}
            </select>
            <Button variant="default" size="sm" icon="spark" onClick={genEmail} disabled={genBusy}>{genBusy ? t('cd.mail.generating') : t('cd.mail.genAI')}</Button>
            <span style={{ fontSize: 11.5, color: '#9aa1ab' }}>{t('cd.mail.genHint')}</span>
          </div>
          {/* AIへのカスタム指示（プロンプト）：知識庫＋会議記録の文脈に加えて、書く方向性を具体的に指定 */}
          <textarea value={instructions} onChange={(e) => setInstructions(e.target.value)} rows={2} placeholder={t('cd.mail.promptPlaceholder')}
            style={{ ...cInput, resize: 'vertical', lineHeight: 1.6, minHeight: 46, marginBottom: 8, background: '#fbfbff' }} />
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {/* To/Cc は入力後に placeholder が消えると見分けが付かないので、常設の短いラベルを左に添える */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ width: 28, flex: '0 0 auto', fontSize: 11.5, fontWeight: 700, color: '#9aa1ab', textAlign: 'right' }}>To</span>
              <input value={to} onChange={(e) => setTo(e.target.value)} placeholder={t('cd.mail.to')} aria-label={t('cd.mail.to')} style={cInput} />
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ width: 28, flex: '0 0 auto', fontSize: 11.5, fontWeight: 700, color: '#9aa1ab', textAlign: 'right' }}>Cc</span>
              <input value={cc} onChange={(e) => setCc(e.target.value)} placeholder={t('cd.mail.cc')} aria-label={t('cd.mail.cc')} style={cInput} />
            </div>
            {/* Ccのボタン式追加：社内メンバーをクリックで出し入れ（担当・サポートを先頭に。自分＝送信者は除く） */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap', paddingLeft: 36 }}>
              {(() => {
                const ccNow = cc.split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
                const pri = [caseData.ownerId, ...(caseData.subIds || [])].filter(Boolean);
                const members = (D.users || []).filter(u => u.email && u.id !== currentUser.id)
                  .sort((a, b) => (pri.indexOf(a.id) < 0 ? 99 : pri.indexOf(a.id)) - (pri.indexOf(b.id) < 0 ? 99 : pri.indexOf(b.id)));
                const toggle = (email) => {
                  const list = cc.split(',').map(s => s.trim()).filter(Boolean);
                  const on = list.some(x => x.toLowerCase() === email.toLowerCase());
                  setCc((on ? list.filter(x => x.toLowerCase() !== email.toLowerCase()) : [...list, email]).join(', '));
                };
                return members.map(u => {
                  const on = ccNow.includes(String(u.email).toLowerCase());
                  const isPri = pri.includes(u.id);
                  return (
                    <button key={u.id} onClick={() => toggle(u.email)} title={u.email}
                      style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 11.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer',
                        padding: '2px 9px', borderRadius: 999, border: '1px solid ' + (on ? '#4a5af0' : '#e2e5ea'),
                        background: on ? '#eef0fe' : '#fff', color: on ? '#4a5af0' : (isPri ? '#3b414b' : '#8a919c') }}>
                      {on ? '✓ ' : '＋ '}{u.short || u.name}
                    </button>
                  );
                });
              })()}
            </div>
            <input value={subject} onChange={(e) => setSubject(e.target.value)} placeholder={t('cd.mail.subject')} style={cInput} />
            <textarea value={bodyText} onChange={(e) => setBodyText(e.target.value)} rows={9} placeholder={t('cd.mail.body')} style={{ ...cInput, resize: 'vertical', lineHeight: 1.7, minHeight: 160 }} />
          </div>
          {/* 添付ファイル */}
          <div style={{ marginTop: 10 }}>
            <input ref={fileRef} type="file" multiple style={{ display: 'none' }} onChange={(e) => { addFiles(e.target.files); e.target.value = ''; }} />
            <Button variant="default" size="sm" icon="plus" onClick={() => fileRef.current && fileRef.current.click()}>{t('cd.mail.attach')}</Button>
            {atts.length > 0 && (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7, marginTop: 8 }}>
                {atts.map((a, i) => (
                  <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: '#eef0fe', color: '#3a49d8', borderRadius: 8, padding: '4px 9px', fontSize: 12, fontWeight: 600, maxWidth: 280 }}>
                    <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.name}</span>
                    <span style={{ color: '#9aa1ab', fontWeight: 500, flex: '0 0 auto' }}>{fmtSize(a.size)}</span>
                    <span onClick={() => removeAtt(i)} style={{ cursor: 'pointer', color: '#9aa1ab', flex: '0 0 auto', fontWeight: 700 }}>×</span>
                  </span>
                ))}
              </div>
            )}
          </div>
          {/* 予約送信：日時を入れると「予約送信」が押せる。プリセットは翌日・翌々日の朝9時／13時（先方の始業に合わせる想定） */}
          <div style={{ display: 'flex', gap: 7, marginTop: 10, alignItems: 'center', flexWrap: 'wrap' }}>
            <span style={{ fontSize: 11.5, fontWeight: 700, color: '#9aa1ab' }}>{t('cd.mail.schedAt')}</span>
            <input type="datetime-local" value={sendAt} min={nowStamp()} onChange={(e) => setSendAt(e.target.value)}
              aria-label={t('cd.mail.schedAt')} style={{ ...cInput, width: 'auto', cursor: 'pointer' }} />
            {[[1, 9, t('cd.mail.schedP1')], [1, 13, t('cd.mail.schedP2')], [2, 9, t('cd.mail.schedP3')]].map(([d, h, label]) => (
              <button key={label} onClick={() => setSendAt(presetAt(d, h))}
                style={{ fontSize: 11.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', padding: '3px 10px', borderRadius: 999, border: '1px solid #e2e5ea', background: '#fff', color: '#8a919c' }}>
                {label}
              </button>
            ))}
            {sendAt && <span style={{ fontSize: 11.5, color: '#aab0ba' }}>{t('cd.mail.schedNote')}</span>}
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 10, alignItems: 'center', flexWrap: 'wrap' }}>
            <Button variant="primary" size="sm" icon="arrowRight" onClick={sendMail} disabled={sendBusy || schedBusy}>{sendBusy ? t('cd.mail.sending') : t('cd.mail.send')}</Button>
            <Button variant="default" size="sm" icon="clock" onClick={scheduleMail} disabled={!sendAt || sendBusy || schedBusy}>{schedBusy ? t('cd.mail.scheduling') : t('cd.mail.schedBtn')}</Button>
            <Button variant="subtle" size="sm" onClick={() => setComposeOpen(false)}>{t('btn.cancel')}</Button>
            <span style={{ fontSize: 11.5, color: '#aab0ba', marginLeft: 'auto' }}>{t('cd.mail.sendNote')}</span>
          </div>
        </div>
      )}
      {!cust.email && (
        <div style={{ padding: '12px 14px', background: '#f6f7f9', borderRadius: 10, fontSize: 12.5, color: '#7b828d', lineHeight: 1.7 }}>
          {t('cd.mailNoEmailInfo')}
        </div>
      )}
      {cust.email && mails.length === 0 && (
        <div style={{ padding: '40px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 13 }}>
          {t('cd.mailEmptyHint', { action: connected ? t('cd.gmailSync') : t('cd.gmailConnectAndSync') })}
        </div>
      )}
      {mails.map((m, i) => {
        const inbound = m.direction === 'in';
        const open = openId === m.id;
        // サーバー保存の本文を優先（同期時取得・全員閲覧可）。無い旧データのみオンデマンド結果を使う
        const body = (m.body != null) ? (m.body || t('cd.mailBodyEmpty')) : bodies[m.gid];
        const isErr = typeof body === 'string' && body.indexOf('ERR:') === 0;
        return (
          <div key={m.id} style={{ borderBottom: i === mails.length - 1 ? 'none' : '1px solid #f4f5f7' }}>
            <div onClick={() => toggle(m)} className="row-hover" style={{ display: 'flex', gap: 12, padding: '12px 6px', cursor: 'pointer', borderRadius: 8 }}>
              <div style={{ width: 30, height: 30, borderRadius: '50%', flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center', background: inbound ? '#eef0ff' : '#e3f5e9' }}>
                <Icon name={inbound ? 'download' : 'arrowRight'} size={15} stroke={2} style={{ color: inbound ? '#4a5af0' : '#16a34a' }} />
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{ fontSize: 10.5, fontWeight: 700, color: inbound ? '#4a5af0' : '#16a34a', background: inbound ? '#eef0ff' : '#e3f5e9', padding: '1px 7px', borderRadius: 4, flex: '0 0 auto' }}>{inbound ? t('cd.mailInbound') : t('cd.mailOutbound')}</span>
                  <span style={{ fontSize: 13.5, fontWeight: 600, color: '#1f2430', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', flex: 1 }}>{m.subject}</span>
                  <Icon name="chevronDown" size={15} stroke={2} style={{ color: '#c4c9d0', flex: '0 0 auto', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
                </div>
                {!open && <div style={{ fontSize: 12, color: '#7b828d', marginTop: 3, lineHeight: 1.6, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{m.snippet}</div>}
                <div style={{ fontSize: 11, color: '#aab0ba', marginTop: 4 }}>{m.date ? fmtDateTime(m.date) : ''} · {inbound ? t('cd.mailFrom', { addr: m.from }) : t('cd.mailTo', { addr: m.to })}</div>
              </div>
            </div>
            {open && (
              <div style={{ margin: '2px 6px 12px 48px', padding: '12px 14px', background: '#fafbfc', border: '1px solid #eef0f3', borderRadius: 10 }}>
                {loadingId === m.id && body == null
                  ? <div style={{ fontSize: 12.5, color: '#9aa1ab' }}>{t('cd.mailLoadingBody')}</div>
                  : isErr
                    ? <div style={{ fontSize: 12.5, color: '#dc2626' }}>{body.slice(4)}</div>
                    : <div style={{ fontSize: 13, color: '#2b2f38', lineHeight: 1.75, whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: 460, overflow: 'auto' }}>{body != null ? body : (m.snippet || t('cd.mailNoBody'))}</div>}
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

/* 商談ごとの「営業担当メモ」インライン編集（クリックで編集 / 未入力時は追加リンク） */
function FeedbackRow({ value, onSave }) {
  const [edit, setEdit] = React.useState(false);
  const [val, setVal] = React.useState(value || '');
  React.useEffect(() => { setVal(value || ''); }, [value]);
  if (edit) {
    return (
      <div style={{ marginTop: 10 }}>
        <textarea value={val} onChange={(e) => setVal(e.target.value)} rows={2} autoFocus
          placeholder={t('case-detail.feedbackPlaceholder')}
          style={{ ...inputStyle, resize: 'vertical', lineHeight: 1.65, fontSize: 14 }} />
        <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
          <Button size="sm" variant="primary" icon="check" onClick={() => { onSave(val.trim()); setEdit(false); }}>{t('btn.save')}</Button>
          <Button size="sm" variant="subtle" onClick={() => { setVal(value || ''); setEdit(false); }}>{t('cd.cancelShort')}</Button>
        </div>
      </div>
    );
  }
  if (value) {
    return (
      <div onClick={() => setEdit(true)} title={t('cd.clickToEditFeedback')}
        style={{ display: 'flex', alignItems: 'flex-start', gap: 8, marginTop: 10, padding: '9px 12px', background: '#f6f6fe', border: '1px solid #e6e4fb', borderRadius: 8, cursor: 'pointer' }}>
        <span style={{ fontSize: 13.5, color: '#4a5af0', fontWeight: 700, whiteSpace: 'nowrap', flex: '0 0 auto', marginTop: 1 }}>{t('case-detail.salesNotes')}</span>
        <span style={{ fontSize: 14, color: '#2b2f38', flex: 1, whiteSpace: 'pre-wrap', lineHeight: 1.7 }}>{value}</span>
        <Icon name="edit" size={14} stroke={2} style={{ color: '#b4bac3', flex: '0 0 auto', marginTop: 2 }} />
      </div>
    );
  }
  return (
    <button onClick={() => setEdit(true)}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 5, marginTop: 8, border: 'none', background: 'transparent', color: '#4a5af0', fontSize: 13.5, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', padding: 0 }}>
      <Icon name="plus" size={14} stroke={2.2} />{t('cd.addSalesNotes')}
    </button>
  );
}

/* 発注者満足度の4段階（ReadyCrew の ordererSatisfaction = 1〜4 に対応） */
const SAT_OPTS = [
  { v: 1, labelKey: 'cd.sat.1' },
  { v: 2, labelKey: 'cd.sat.2' },
  { v: 3, labelKey: 'cd.sat.3' },
  { v: 4, labelKey: 'cd.sat.4' },
];

/* 発注者の回答（ReadyCrew 初回商談アンケート）：ReadyCrew で記入済みの回答を取込んで表示（読取専用） */
function OrdererReview({ caseData }) {
  const r = caseData.ordererReview || null;
  const sat = (r && r.satisfaction) || 0;
  const fb = (r && r.feedback || '').trim();
  const pdate = (r && r.proposalDate) || '';
  const has = !!(sat || fb || pdate);
  const labelStyle = { width: 104, flex: '0 0 auto', fontSize: 13.5, fontWeight: 700, color: '#1c1f26', paddingTop: 2 };

  // 未取込：ReadyCrew案件のみ、取込待ちの控えめな案内（手入力はしない）
  if (!has) {
    if (caseData.source !== 'scrape') return null;
    return (
      <div style={{ marginBottom: 16, padding: '10px 13px', background: '#faf7f8', border: '1px dashed #e7dade', borderRadius: 10, fontSize: 12, color: '#9a8e92', display: 'flex', alignItems: 'center', gap: 7 }}>
        <Icon name="link" size={12} stroke={2} />{t('cd.ordererReviewPending')}
      </div>
    );
  }

  return (
    <div style={{ marginBottom: 18 }}>
      <Card pad={18}
        title={<span style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}>
          <span style={{ fontSize: 11, fontWeight: 800, color: '#fff', background: '#d8324a', padding: '3px 9px', borderRadius: 5, letterSpacing: '.04em' }}>{t('cd.firstMeeting')}</span>
          {t('cd.ordererReview')}
        </span>}
        action={<span style={{ fontSize: 12, color: '#9aa1ab', display: 'inline-flex', alignItems: 'center', gap: 4 }}><Icon name="link" size={12} stroke={2} />{t('cases.readycrewImport')}</span>}
      >
        {/* 発注者満足度（4段ステッパー・読取専用） */}
        <div style={{ display: 'flex', alignItems: 'flex-start', marginBottom: 22 }}>
          <div style={labelStyle}>{t('cd.ordererSatisfaction')}</div>
          <div style={{ flex: 1, display: 'flex', paddingTop: 2, minWidth: 0 }}>
            {SAT_OPTS.map((o, i) => {
              const on = sat === o.v;
              const oc = (satMeta(o.v) || {}).color || '#d8324a'; // 満足度に応じた色（一覧バッジ・分析と統一）
              return (
                <div key={o.v} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative', minWidth: 0 }}>
                  {i < SAT_OPTS.length - 1 && <div style={{ position: 'absolute', top: 10, left: '50%', width: '100%', height: 2, background: '#e6e8ec' }} />}
                  <div style={{ width: 22, height: 22, borderRadius: '50%', border: '2px solid ' + (on ? oc : '#cfd4da'), background: on ? oc : '#fff', zIndex: 1, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: on ? `0 0 0 3px ${oc}26` : 'none' }}>
                    {on && <div style={{ width: 7, height: 7, borderRadius: '50%', background: '#fff' }} />}
                  </div>
                  <span style={{ fontSize: 11.5, marginTop: 8, color: on ? oc : '#9aa1ab', fontWeight: on ? 700 : 500, textAlign: 'center', lineHeight: 1.4 }}>{t(o.labelKey)}</span>
                </div>
              );
            })}
          </div>
        </div>

        {/* 感想（発注者の自由記述・読取専用） */}
        <div style={{ display: 'flex', alignItems: 'flex-start', marginBottom: 18 }}>
          <div style={labelStyle}>{t('cd.impression')}</div>
          <div style={{ flex: 1, minWidth: 0, fontSize: 13, color: fb ? '#3b414b' : '#b4bac3', lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>{fb || t('cd.noAnswer')}</div>
        </div>

        {/* 提案予定日（読取専用） */}
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <div style={labelStyle}>{t('cd.proposalDate')}</div>
          <div style={{ flex: 1 }}>
            {pdate ? <span style={{ fontSize: 13.5, fontWeight: 700, color: '#4a5af0' }}>{fmtDateFull(pdate)}</span>
                   : <span style={{ fontSize: 13.5, fontWeight: 700, color: '#d8324a' }}>{t('cd.notEntered')}</span>}
          </div>
        </div>
      </Card>
    </div>
  );
}

Object.assign(window, { NoteEditor, MailTab, FeedbackRow, OrdererReview });
