/* ============================================================
   案件詳細 — 提案・見積タブ（DocTab / ProposalAnalysis / FeatureList）。case-detail.jsx から分離（依存はグローバル＝render時解決）。
   ※ AI分析の結果は固定高さ枠＋内部スクロール（ProposalAnalysis の maxHeight）。
   ============================================================ */
const PREP_ORDER = [
  { k: 'direction', labelKey: 'cd.prep.direction' },
  { k: 'outline', labelKey: 'cd.prep.outline', outline: true },
  { k: 'threat', labelKey: 'cd.prep.threat' },
  { k: 'expectation', labelKey: 'cd.prep.expectation' },
  { k: 'differentiation', labelKey: 'cd.prep.differentiation' },
  { k: 'valueExec', labelKey: 'cd.prep.valueExec' },
  { k: 'valueManager', labelKey: 'cd.prep.valueManager' },
  { k: 'valueStaff', labelKey: 'cd.prep.valueStaff' },
  { k: 'roi', labelKey: 'cd.prep.roi', roi: true },
  { k: 'resonated', labelKey: 'cd.prep.resonated' },
  { k: 'concerns', labelKey: 'cd.prep.concerns' },
  { k: 'competition', labelKey: 'cd.prep.competition', blocks: true },
  { k: 'qa', labelKey: 'cd.prep.qa', blocks: true },
  { k: 'demoScreens', labelKey: 'cd.prep.demoScreens', blocks: true },
  { k: 'closer', labelKey: 'cd.prep.closer' },
  { k: 'mustHave', labelKey: 'cd.prep.mustHave' },
  { k: 'timelineBudget', labelKey: 'cd.prep.timelineBudget' },
];
/* 会議記録からのAI分析（見積書の下準備）。case.quoteAnalysis に保存される。
   会議記録（顧客ニーズ）× 見積もり用ナレッジ（自社のデモ・見積書の型・料金プラン）から機能清單を作成 */
const QUOTE_ORDER = [
  { k: 'features', labelKey: 'cd.quote.features', features: true },
  { k: 'mustWant', labelKey: 'cd.quote.mustWant' },
  { k: 'costDrivers', labelKey: 'cd.quote.costDrivers', list: true },
  { k: 'risks', labelKey: 'cd.quote.risks', list: true },
  { k: 'phasing', labelKey: 'cd.quote.phasing' },
  { k: 'budgetSignal', labelKey: 'cd.quote.budgetSignal' },
  { k: 'negotiation', labelKey: 'cd.quote.negotiation' },
  { k: 'basis', labelKey: 'cd.quote.basis', list: true, muted: true },
];
/* プロトタイプのAI分析：会議記録(＋プロトタイプ)から本番システムに必要な全機能・要件定義・附加価値・抜け漏れ論点を出す。case.prototypeAnalysis に保存 */
const PROTOTYPE_ORDER = [
  { k: 'overview', labelKey: 'cd.proto.overview' },
  { k: 'features', labelKey: 'cd.proto.features', features: true },
  { k: 'screenFlow', labelKey: 'cd.proto.screenFlow', screens: true },
  { k: 'dataModel', labelKey: 'cd.proto.dataModel', data: true },
  { k: 'requirements', labelKey: 'cd.proto.requirements', list: true },
  { k: 'addedValue', labelKey: 'cd.proto.addedValue', list: true },
  { k: 'hiddenIssues', labelKey: 'cd.proto.hiddenIssues', list: true },
];
/* 商談提案（次の商談プレイブック）：商談記録＋提案/見積もり分析から「次の商談の進め方」を提案。case.shodanPrep に保存 */
const SHODAN_ORDER = [
  { k: 'objective', labelKey: 'cd.shodan.objective' },
  { k: 'agenda', labelKey: 'cd.shodan.agenda', blocks: true },
  { k: 'keyPitch', labelKey: 'cd.shodan.keyPitch' },
  { k: 'showOrder', labelKey: 'cd.shodan.showOrder', list: true },
  { k: 'qa', labelKey: 'cd.shodan.qa', blocks: true },
  { k: 'closing', labelKey: 'cd.shodan.closing' },
  { k: 'nextAction', labelKey: 'cd.shodan.nextAction', list: true },
  { k: 'watchouts', labelKey: 'cd.shodan.watchouts', list: true },
];
/* 商談後：会議記録をもとに、デモ改修の要否・直し方（具体・優先度）・顧客の本音を出す。case.protoFeedback に保存 */
const PROTO_FEEDBACK_ORDER = [
  { k: 'verdict', labelKey: 'cd.pf.verdict' },
  { k: 'customerNeeds', labelKey: 'cd.pf.customerNeeds', list: true },
  { k: 'changes', labelKey: 'cd.pf.changes', changes: true },
  { k: 'keep', labelKey: 'cd.pf.keep', list: true },
  { k: 'risks', labelKey: 'cd.pf.risks', list: true },
];
const ANALYSIS_KINDS = {
  proposal: { field: 'proposalAnalysis', order: PREP_ORDER, apiFn: (id) => API.proposalPrep(id),
    headKey: 'cd.analysis.proposal.head', subKey: 'cd.analysis.proposal.sub', emptyKey: 'cd.analysis.proposal.empty' },
  quote: { field: 'quoteAnalysis', order: QUOTE_ORDER, apiFn: (id) => API.quotePrep(id),
    headKey: 'cd.analysis.quote.head', subKey: 'cd.analysis.quote.sub', emptyKey: 'cd.analysis.quote.empty' },
  prototype: { field: 'prototypeAnalysis', order: PROTOTYPE_ORDER, apiFn: (id) => API.prototypePrep(id),
    headKey: 'cd.analysis.prototype.head', subKey: 'cd.analysis.prototype.sub', emptyKey: 'cd.analysis.prototype.empty' },
  shodan: { field: 'shodanPrep', order: SHODAN_ORDER, apiFn: (id) => API.shodanPrep(id),
    headKey: 'cd.analysis.shodan.head', subKey: 'cd.analysis.shodan.sub', emptyKey: 'cd.analysis.shodan.empty' },
  protoFeedback: { field: 'protoFeedback', order: PROTO_FEEDBACK_ORDER, apiFn: (id) => API.protoFeedback(id),
    headKey: 'cd.analysis.protoFeedback.head', subKey: 'cd.analysis.protoFeedback.sub', emptyKey: 'cd.analysis.protoFeedback.empty' },
};
const SCALE_META = { S: { label: 'S', color: '#16a34a', bg: '#e3f5e9' }, M: { label: 'M', color: '#b45309', bg: '#fdf0db' }, L: { label: 'L', color: '#b91c1c', bg: '#fdecec' } };
/* 機能清單（features）レンダラ：name + MUST/WANTバッジ + 規模S/M/L + プラン + 説明 */
function FeatureList({ items, titleKey }) {
  const arr = Array.isArray(items) ? items : [];
  if (!arr.length) return null;
  const mustCount = arr.filter(f => f.must).length;
  return (
    <div style={{ marginBottom: 16 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 6 }}>
        <span style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed' }}>{t(titleKey || 'cd.quote.features')}</span>
        <span style={{ fontSize: 11.5, color: '#a8aeb8' }}>{t('cd.featureSummary', { count: arr.length, must: mustCount, want: arr.length - mustCount })}</span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {arr.map((f, i) => {
          const sc = SCALE_META[f.scale];
          return (
            <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '9px 12px', background: '#fff' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                <span style={{ fontSize: 10.5, fontWeight: 700, color: f.must ? '#b91c1c' : '#6b7280', background: f.must ? '#fdecec' : '#eef0f2', padding: '1px 7px', borderRadius: 999, flex: '0 0 auto' }}>{f.must ? t('cd.must') : t('cd.want')}</span>
                <span style={{ fontSize: 13.5, fontWeight: 700, color: '#1c1f26' }}>{f.name}</span>
                {sc && <span style={{ fontSize: 10.5, fontWeight: 700, color: sc.color, background: sc.bg, padding: '1px 7px', borderRadius: 999, flex: '0 0 auto' }} title={t('cd.scaleTitle')}>{t('cd.scaleBadge', { scale: sc.label })}</span>}
                {f.plan && <span style={{ fontSize: 11, fontWeight: 600, color: '#4a5af0', background: '#eef0fe', padding: '1px 8px', borderRadius: 999, flex: '0 0 auto' }}>{f.plan}</span>}
                {f.screen && <span style={{ fontSize: 10.5, fontWeight: 600, color: '#0e7490', background: '#e0f2fe', padding: '1px 7px', borderRadius: 999, flex: '0 0 auto' }} title={t('cd.proto.screen')}>{f.screen}</span>}
              </div>
              {f.desc && <div style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.6, marginTop: 4 }}>{f.desc}</div>}
              {Array.isArray(f.roles) && f.roles.length > 0 && <div style={{ fontSize: 11, color: '#7b828d', marginTop: 4 }}>{t('cd.proto.roles')}: {f.roles.join('・')}</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}
function ProposalAnalysis({ caseData, kind = 'proposal' }) {
  const { showToast, patchCase } = useStore();
  const cfg = ANALYSIS_KINDS[kind] || ANALYSIS_KINDS.proposal;
  const a = caseData[cfg.field] || null;
  const [busy, setBusy] = React.useState(false);
  const run = async () => {
    if (busy) return; setBusy(true);
    try {
      const res = await cfg.apiFn(caseData.id);
      patchCase(caseData.id, { [cfg.field]: res.analysis });
      showToast(t('cd.toast.analysisCreated'));
    } catch (e) { showToast(e.message || t('cd.toast.analysisFailed'), 'x'); }
    setBusy(false);
  };
  return (
    <div style={{ marginBottom: 24 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
        <Icon name="spark" size={15} fill="#7c3aed" style={{ color: '#7c3aed', flex: '0 0 auto' }} />
        <span style={{ fontSize: 12.5, fontWeight: 700, color: '#1c1f26', whiteSpace: 'nowrap' }}>{t(cfg.headKey)}</span>
        <span style={{ fontSize: 12, color: '#a8aeb8' }}>{t(cfg.subKey)}</span>
        <div style={{ marginLeft: 'auto' }}>
          <Button size="sm" variant={a ? 'default' : 'primary'} icon={busy ? 'refresh' : 'spark'} onClick={run} disabled={busy}>
            {busy ? t('cd.analyzing') : (a ? t('cd.reanalyze') : t('cd.generateAnalysis'))}
          </Button>
        </div>
      </div>
      {!a && !busy && (
        <div style={{ padding: '16px 14px', background: '#faf7ff', border: '1px dashed #e2d7f5', borderRadius: 10, fontSize: 12.5, color: '#8a7ba8', lineHeight: 1.75 }}>
          {t(cfg.emptyKey)}
        </div>
      )}
      {busy && <div style={{ padding: '22px 0', textAlign: 'center', color: '#9aa1ab', fontSize: 12.5 }}>{t('cd.analyzingInProgress')}</div>}
      {a && !busy && (
        <div style={{ border: '1px solid #ece7f6', borderRadius: 12, padding: 16, background: '#fcfbfe', maxHeight: 400, overflowY: 'auto', overscrollBehavior: 'contain' }}>
          {cfg.order.map(r => {
            if (r.features) return <FeatureList key={r.k} items={a[r.k]} titleKey={r.labelKey} />;
            if (r.screens) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              return (
                <div key={r.k} style={{ marginBottom: 16 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 6 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {arr.map((s, i) => (
                      <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '8px 12px', background: '#fff' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                          <span style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}>{s.screen}</span>
                          {s.role && <span style={{ fontSize: 10.5, fontWeight: 600, color: '#4a5af0', background: '#eef0fe', padding: '1px 7px', borderRadius: 999, flex: '0 0 auto' }}>{s.role}</span>}
                        </div>
                        {s.purpose && <div style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.6, marginTop: 3 }}>{s.purpose}</div>}
                        {Array.isArray(s.to) && s.to.length > 0 && <div style={{ fontSize: 12, color: '#7b828d', marginTop: 4, display: 'flex', flexWrap: 'wrap', gap: '2px 10px' }}>{s.to.map((d, j) => <span key={j}>→ {d}</span>)}</div>}
                      </div>
                    ))}
                  </div>
                </div>
              );
            }
            if (r.data) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              return (
                <div key={r.k} style={{ marginBottom: 16 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 6 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {arr.map((d, i) => (
                      <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '8px 12px', background: '#fff' }}>
                        <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}>{d.entity}</div>
                        {Array.isArray(d.fields) && d.fields.length > 0 && <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginTop: 5 }}>{d.fields.map((fl, j) => <span key={j} style={{ fontSize: 11, color: '#5a616c', background: '#f3f0fa', padding: '1px 7px', borderRadius: 6 }}>{fl}</span>)}</div>}
                        {Array.isArray(d.relations) && d.relations.length > 0 && <div style={{ fontSize: 12, color: '#7b828d', marginTop: 5, lineHeight: 1.6 }}>{d.relations.map((rl, j) => <div key={j}>↔ {rl}</div>)}</div>}
                      </div>
                    ))}
                  </div>
                </div>
              );
            }
            if (r.blocks) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              return (
                <div key={r.k} style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 6 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {arr.map((b, i) => (
                      <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '8px 12px', background: '#fff' }}>
                        {b.head && <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}>{b.head}</div>}
                        {b.body && <div style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.6, marginTop: b.head ? 3 : 0, whiteSpace: 'pre-wrap' }}>{b.body}</div>}
                      </div>
                    ))}
                  </div>
                </div>
              );
            }
            if (r.outline) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              return (
                <div key={r.k} style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 6 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {arr.map((s, i) => (
                      <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '8px 12px', background: '#fff' }}>
                        <div style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}><span style={{ color: '#7c3aed' }}>{i + 1}.</span> {s.section}</div>
                        {Array.isArray(s.points) && s.points.length > 0 && <div style={{ marginTop: 4, display: 'flex', flexDirection: 'column', gap: 2 }}>{s.points.map((p, j) => <div key={j} style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.55, display: 'flex', gap: 6 }}><span style={{ color: '#c4b5e8' }}>・</span><span style={{ whiteSpace: 'pre-wrap' }}>{p}</span></div>)}</div>}
                      </div>
                    ))}
                  </div>
                </div>
              );
            }
            if (r.roi) {
              const o = a[r.k];
              if (!o || (!o.summary && !(Array.isArray(o.basis) && o.basis.length))) return null;
              return (
                <div key={r.k} style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 5 }}>{t(r.labelKey)}</div>
                  {o.summary && <div style={{ fontSize: 13.5, color: '#2b2f38', lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>{o.summary}</div>}
                  {Array.isArray(o.basis) && o.basis.length > 0 && <div style={{ marginTop: 5, display: 'flex', flexDirection: 'column', gap: 3 }}>{o.basis.map((b, j) => <div key={j} style={{ fontSize: 12.5, color: '#5a616c', lineHeight: 1.55, display: 'flex', gap: 6 }}><span style={{ color: '#7c3aed', fontWeight: 700 }}>›</span><span>{b}</span></div>)}</div>}
                </div>
              );
            }
            if (r.changes) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              const PRI = { '高': { c: '#b91c1c', b: '#fdecec' }, '中': { c: '#d97706', b: '#fdf0db' }, '低': { c: '#6b7280', b: '#eef0f2' } };
              return (
                <div key={r.k} style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 6 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    {arr.map((ch, i) => {
                      const p = PRI[ch.priority] || PRI['中'];
                      return (
                        <div key={i} style={{ border: '1px solid #efe9fb', borderRadius: 9, padding: '9px 12px', background: '#fff' }}>
                          <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                            {ch.priority && <span style={{ fontSize: 10.5, fontWeight: 700, color: p.c, background: p.b, padding: '1px 8px', borderRadius: 999, flex: '0 0 auto' }}>{ch.priority}</span>}
                            <span style={{ fontSize: 13, fontWeight: 700, color: '#1c1f26' }}>{ch.target}</span>
                          </div>
                          {ch.change && <div style={{ fontSize: 12.5, color: '#3b414b', lineHeight: 1.6, marginTop: 4, whiteSpace: 'pre-wrap' }}>{ch.change}</div>}
                          {ch.reason && <div style={{ fontSize: 12, color: '#9aa1ab', lineHeight: 1.55, marginTop: 3 }}>{t('cd.pf.reasonLabel')}：{ch.reason}</div>}
                        </div>
                      );
                    })}
                  </div>
                </div>
              );
            }
            if (r.list) {
              const arr = Array.isArray(a[r.k]) ? a[r.k] : [];
              if (!arr.length) return null;
              const accent = r.muted ? '#9aa1ab' : '#7c3aed';
              return (
                <div key={r.k} style={{ marginBottom: 14 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 700, color: accent, marginBottom: 5 }}>{t(r.labelKey)}</div>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
                    {arr.map((s, i) => (
                      <div key={i} style={{ fontSize: r.muted ? 12 : 13.5, color: r.muted ? '#7b828d' : '#2b2f38', lineHeight: 1.6, display: 'flex', gap: 7 }}>
                        <span style={{ color: accent, fontWeight: 700, flex: '0 0 auto' }}>{i + 1}.</span><span style={{ whiteSpace: 'pre-wrap' }}>{s}</span>
                      </div>
                    ))}
                  </div>
                </div>
              );
            }
            return a[r.k] ? (
              <div key={r.k} style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 12.5, fontWeight: 700, color: '#7c3aed', marginBottom: 4 }}>{t(r.labelKey)}</div>
                <div style={{ fontSize: 13.5, color: '#2b2f38', lineHeight: 1.75, whiteSpace: 'pre-wrap' }}>{a[r.k]}</div>
              </div>
            ) : null;
          })}
          {a.generatedAt && <div style={{ fontSize: 11.5, color: '#b4bac3', marginTop: 2 }}>{t('cd.generatedAt', { at: a.generatedAt })}{a.sourceDocs != null ? ' · ' + t('cd.sourceDocsCount', { n: a.sourceDocs }) : ''}{a.refCases ? ' · ' + t('cd.refCasesCount', { n: a.refCases }) : ''}{a.refChunks ? ' · ' + t('cd.refChunksCount', { n: a.refChunks }) : ''}</div>}
        </div>
      )}
    </div>
  );
}

/* freee形式の編集できる見積表。AI(機能清單)から取込→数量・単価を入力→freeeに送信。case.quoteSheet に自動保存 */
/* QuoteSheet（見積表）は src/case-quote.jsx へ分離 */

/* canAdd＝追加系（ファイル添付・リンク登録）のみの権限（prototypeAdd）。editable＝編集・管理（ステータス/担当/期限も） */
/* 提案書ブリーフまるごと生成カード（社内スキル alion-proposal-brief のアプリ内版・提案書タブ専用）。
   サーバがスキル原本（8セクション構成・固定スライド逐語・文章ルール）を適用して完成原稿のMarkdownを生成。
   生成物は case.proposalBrief {text, at, v}。コピー／.mdダウンロード（後工程のClaude Design/Canvaへ渡す）。 */
function ProposalBriefCard({ caseData }) {
  const { patchCase, showToast, currentUser, navigate } = useStore();
  const pb = caseData.proposalBrief || null;
  const skillSelection=useProposalSkillSelection(caseData);
  const [busy, setBusy] = React.useState(false);
  const [open, setOpen] = React.useState(false);
  const [inst, setInst] = React.useState(''); // 営業担当からの特別指示（最優先で反映・任意）
  /* 提案書テンプレート（ナレッジ＞AI分析設定で登録）。選ぶとその章立て・枚数どおりに生成される */
  const [tpls, setTpls] = React.useState([]);
  const [tplId, setTplId] = React.useState('');
  const [deckOpen, setDeckOpen] = React.useState(false);   // 提案書デザイン画面
  const [stepMenu, setStepMenu] = React.useState(null);    // 開いている「⋯」メニュー（'1'|'2'|'3'）
  React.useEffect(() => {
    if (!stepMenu) return;
    const close = () => setStepMenu(null);
    window.addEventListener('click', close);
    return () => window.removeEventListener('click', close);
  }, [stepMenu]);
  React.useEffect(() => {
    let alive = true;
    API.proposalTemplates().then(r => {
      if (!alive) return;
      const list = (r && r.templates) || [];
      setTpls(list);
      const def = list.find(x => x.isDefault);
      if (def) setTplId(def.id);
    }).catch(() => {});
    return () => { alive = false; };
  }, []);
  const gen = async () => {
    if (busy||!skillSelection.readyFor('brief')) return;
    if (pb && !window.confirm(t('cd.brief.regenConfirm'))) return;
    setBusy(true);
    try {
      const r = await API.proposalBriefGen(caseData.id, inst.trim() || undefined,skillSelection.value.brief);
      patchCase(caseData.id, { proposalBrief: { text: r.text, at: r.at || '', v: r.v || '', skill:r.skill, warnings: r.warnings || [] } });
      setOpen(true); showToast(t('cd.brief.done'));
    } catch (e) { showToast((e && e.message) || t('cd.brief.failed'), 'x'); }
    setBusy(false);
  };
  const dl = () => {
    const cust = window.APP_DATA.customer(caseData.customerId) || {};
    const blob = new Blob([pb.text], { type: 'text/markdown' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `${today()}_${String(cust.company || caseData.title || 'client').replace(/[\\/:*?"<>|]/g, '').slice(0, 30)}_提案書ブリーフ.md`;
    a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 4000);
  };
  // デザインPPTX生成（サーバ非同期ジョブ→完了で添付に追加）。仕上げはCanvaへインポートして行う
  const [dzBusy, setDzBusy] = React.useState(false);
  const pollRef = React.useRef(null);
  React.useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current); }, []);
  const startPoll = () => {
    if (pollRef.current) clearInterval(pollRef.current);
    pollRef.current = setInterval(async () => {
      try {
        const s = await API.proposalDesignStatus(caseData.id);
        if (s.running) return;
        clearInterval(pollRef.current); pollRef.current = null; setDzBusy(false);
        if (s.error) { showToast(s.error, 'x'); return; }
        if (s.attId) {
          const patch = { proposalDesign: s.design || { ...caseData.proposalDesign,attId: s.attId, at: s.at || '', v: s.v || '',skill:s.skill } };
          if (s.att && !(caseData.attachments || []).some(x => x.id === s.att.id)) patch.attachments = [s.att, ...(caseData.attachments || [])];
          patchCase(caseData.id, patch);
          showToast(t('cd.brief.designDone'));
        }
      } catch (_) { /* 一時的な通信エラーは次のポーリングで回復 */ }
    }, 8000);
  };
  const genDesign = async () => {
    if (dzBusy || busy ||!skillSelection.readyFor('design')) return;
    if (caseData.proposalDesign && caseData.proposalDesign.attId && !window.confirm(t('cd.brief.designRegenConfirm'))) return;
    setDzBusy(true);
    try { await API.proposalDesignStart(caseData.id, tplId || undefined,skillSelection.value.design); showToast(t('cd.brief.designStarted')); startPoll(); }
    catch (e) { setDzBusy(false); showToast((e && e.message) || t('cd.brief.failed'), 'x'); }
  };
  // 共用CanvaへPPTXを自動インポート→編集画面を開く。未連携なら管理者だけOAuth承認へ誘導
  const [cvBusy, setCvBusy] = React.useState(false);
  const canvaPush = async () => {
    if (cvBusy) return; setCvBusy(true);
    try {
      const r = await API.canvaPush(caseData.id);
      if (r && r.editUrl) {
        patchCase(caseData.id, { canvaDesign: { id: r.designId || '', url: r.editUrl, at: '' } });
        window.open(r.editUrl, '_blank');
        showToast(r.borrowed && r.account ? `Canvaにデザインを作成しました（${r.account} さんのCanva）` : 'Canvaにデザインを作成しました');
      } else showToast(window.t("label.extra22"), 'x');
    } catch (e) {
      if (e && e.status === 428 && currentUser && currentUser.role === 'admin') {
        try { const u = await API.canvaAuthUrl(); window.open(u.url, '_blank'); showToast(window.t("label.extra23")); }
        catch (e2) { showToast((e2 && e2.message) || 'Canva連携を開始できませんでした', 'x'); }
      } else showToast((e && e.message) || 'Canvaへの送信に失敗しました', 'x');
    }
    setCvBusy(false);
  };
  return (
    <div style={{ border: '1px solid #e6e4fb', background: '#fbfbff', borderRadius: 12, padding: '14px 16px', marginBottom: 24 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        <Icon name="spark" size={15} stroke={2.2} style={{ color: '#4a5af0', flex: '0 0 auto' }} />
        <span style={{ fontSize: 13, fontWeight: 700, color: '#3a49d8' }}>{t('cd.brief.title')}</span>
        {pb && pb.v && <span style={{ fontSize: 10.5, fontWeight: 700, color: '#7b828d', background: '#eef1f5', padding: '1px 7px', borderRadius: 999 }}>{pb.v}</span>}
        {/* 「作り直す」がヘッダと②の2箇所で並ぶと押す物が分からなくなるため、
            ヘッダのボタンは初回（ブリーフ未作成）だけ。作成後のやり直しは①の行の小リンクへ */}
        <span style={{ marginLeft: 'auto', display: 'flex', gap: 8, alignItems: 'center' }}>
          {pb && <span style={{ fontSize: 10.5, color: '#b4bac3' }}>{String(pb.at || '').slice(5, 16).replace('T', ' ')}</span>}
          {!pb && (
            <Button variant="primary" size="sm" icon="spark" onClick={gen} disabled={busy||!skillSelection.readyFor('brief')}>
              {busy ? t('cd.brief.generating') : t('cd.brief.generate')}
            </Button>
          )}
        </span>
      </div>
      <ProposalSkillPicker selection={skillSelection} disabled={busy||dzBusy}/>
      {pb?.skill&&<UsedProposalSkill skill={pb.skill}/>}
      <textarea value={inst} onChange={e => setInst(e.target.value)} rows={2} placeholder={t('cd.brief.instPlaceholder')}
        style={{ ...inputStyle, fontSize: 12.5, marginTop: 8, resize: 'vertical', lineHeight: 1.6, background: '#fff' }} />
      {/* 進行中の説明文は出さない（ボタン側の「生成中…」表示だけで十分、というユーザー要望） */}
      {deckOpen && <DeckEditor caseData={caseData} onClose={() => setDeckOpen(false)} />}
      {/* 「要確認：案件情報に見当たらない語…」の警告バナーは廃止（スライドタイトル・ブロック等の
         汎用語ばかり拾う誤検知でノイズになっていた、というユーザー要望。データ pb.warnings は残る） */}
      {pb && (
        <div style={{ marginTop: 10 }}>
          {/* 手順 ①→②→③。各行＝見出し＋主ボタン1つ＋「⋯」。補助操作（作り直す・コピー・DL・並び…）は
             ⋯メニューに畳む。リンクを並べると「どれを押すか分からない」ため（ユーザー要望） */}
          {(() => {
            const hasDesign = !!(caseData.proposalDesign && caseData.proposalDesign.attId);
            const hasCanva = !!(caseData.canvaDesign && caseData.canvaDesign.url);
            const mItem = (label, onClick, opts = {}) => (
              <button key={label} onClick={() => { setStepMenu(null); onClick(); }} disabled={!!opts.disabled}
                onMouseEnter={e => { e.currentTarget.style.background = '#f4f4fb'; }} onMouseLeave={e => { e.currentTarget.style.background = 'none'; }}
                style={{ textAlign: 'left', fontSize: 12.5, padding: '7px 10px', borderRadius: 7, border: 'none', background: 'none',
                  cursor: opts.disabled ? 'default' : 'pointer', color: opts.disabled ? '#b4bac3' : '#3b414b', fontFamily: 'inherit' }}>{label}</button>
            );
            const dots = (id, children) => (
              <span style={{ position: 'relative', flex: '0 0 auto' }} onClick={e => e.stopPropagation()}>
                <button onClick={() => setStepMenu(m => (m === id ? null : id))} title={window.t("extra.common.otherActions")}
                  style={{ width: 30, height: 30, borderRadius: 8, border: '1px solid #e6e4f2', background: stepMenu === id ? '#eef0fd' : '#fff',
                    color: '#7b828d', cursor: 'pointer', fontSize: 15, lineHeight: 1, fontWeight: 700 }}>⋯</button>
                {stepMenu === id && (
                  <div style={{ position: 'absolute', left: 0, top: 34, zIndex: 60, background: '#fff', border: '1px solid #e6e4f5', borderRadius: 10,
                    boxShadow: '0 10px 28px rgba(30,32,60,.14)', padding: 6, minWidth: 216, display: 'flex', flexDirection: 'column' }}>
                    {children}
                  </div>
                )}
              </span>
            );
            const step = (no, label, done, main, menu, note) => (
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 0', borderTop: no === '①' ? 'none' : '1px solid #f0eefb', flexWrap: 'wrap' }}>
                <span style={{ width: 22, height: 22, borderRadius: '50%', flex: '0 0 auto', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11.5, fontWeight: 700, background: done ? '#e8f5ed' : '#eef0fe', color: done ? '#1f7a43' : '#4a5af0' }}>{done ? '✓' : no}</span>
                <span style={{ fontSize: 12.5, fontWeight: 700, color: '#3b414b', width: 132, flex: '0 0 auto' }}>{label}</span>
                {main}
                {menu}
                {note && <span style={{ fontSize: 11.5, color: '#b4bac3' }}>{note}</span>}
              </div>
            );
            return (
              <div style={{ background: '#fff', border: '1px solid #eef0fe', borderRadius: 10, padding: '4px 14px 6px' }}>
                {step('①', 'ブリーフを確認', true,
                  <Button variant="default" size="sm" onClick={() => setOpen(o => !o)}>{open ? t('cd.brief.hide') : t('cd.brief.show')}</Button>,
                  dots('1', <>
                    {mItem(busy ? t('cd.brief.generating') : t('cd.brief.regen'), gen, { disabled: busy||!skillSelection.readyFor('brief') })}
                    {mItem(t('cd.brief.copy'), () => { copyText(pb.text); showToast(t('cd.brief.copied')); })}
                    {mItem(t('cd.brief.download'), dl)}
                  </>))}
                {caseData.proposalDesign?.skill&&<UsedProposalSkill label={window.t("studio.designSkill")} skill={caseData.proposalDesign.skill}/>}
                {step('②', 'デザインを作る', hasDesign,
                  hasDesign
                    ? <Button variant="primary" size="sm" icon="spark" onClick={() => setDeckOpen(true)} disabled={dzBusy}>{dzBusy ? t('cd.brief.designGenerating') : t('cd.deckOpen')}</Button>
                    : <Button variant="primary" size="sm" icon="spark" onClick={genDesign} disabled={dzBusy || busy||!skillSelection.readyFor('design')}>{dzBusy ? t('cd.brief.designGenerating') : t('cd.brief.designGenerate')}</Button>,
                  dots('2', <>
                    {hasDesign && mItem(dzBusy ? t('cd.brief.designGenerating') : '作り直す', genDesign, { disabled: dzBusy || busy||!skillSelection.readyFor('design') })}
                    {hasDesign && mItem(t('cd.brief.openStudio'), () => navigate('proposalStudio', caseData.id))}
                    {hasDesign && mItem(t('cd.brief.designOpen'), () => window.open(API.attachmentUrl(caseData.proposalDesign.attId), '_blank'))}
                    {/* Canva（最終仕上げ・任意）も行を分けずここに収める（ユーザー要望） */}
                    {hasCanva
                      ? <>
                          {mItem('Canvaで開く ↗', () => window.open(caseData.canvaDesign.url, '_blank'))}
                          {mItem(cvBusy ? 'Canvaに送信中…' : '最新デザインをCanvaへ再送信', canvaPush, { disabled: cvBusy })}
                        </>
                      : mItem(cvBusy ? 'Canvaに送信中…' : 'Canvaで仕上げる（任意）', canvaPush, { disabled: cvBusy || !hasDesign })}
                    {tpls.length > 0 && (
                      <div style={{ padding: '6px 10px 4px', display: 'flex', flexDirection: 'column', gap: 4, borderTop: '1px solid #f0eefb', marginTop: 4 }}>
                        <span style={{ fontSize: 11, color: '#8b919b' }}>{window.t("extra.proposal.orderHint")}</span>
                        <select value={tplId} onChange={e => setTplId(e.target.value)} disabled={dzBusy} title={t('cd.tplTip')}
                          style={{ fontSize: 11.5, padding: '4px 8px', borderRadius: 8, border: '1px solid #e4e0f5', color: '#3b414b', background: '#fff', maxWidth: 220 }}>
                          <option value="">{t('cd.tplNone')}</option>
                          {tpls.map(x => <option key={x.id} value={x.id}>{x.name}{x.isDefault ? t('cd.tplDefault') : ''}</option>)}
                        </select>
                      </div>
                    )}
                  </>))}
              </div>
            );
          })()}
          {open && (
            <div style={{ marginTop: 10, maxHeight: 480, overflow: 'auto', background: '#fff', border: '1px solid #eef0fe', borderRadius: 10, padding: '12px 15px', fontSize: 12, color: '#2b2f38', lineHeight: 1.75, whiteSpace: 'pre-wrap', fontFamily: 'inherit' }}>{pb.text}</div>
          )}
        </div>
      )}
    </div>
  );
}

function DocTab({ caseData, category, statusField, ownerField, dueField, title, linksField, analysis, analysis2, editable = true, canAdd = false }) {
  const allowAdd = editable || canAdd;
  const { showToast, patchCase, currentUser, saveCase, navigate } = useStore();
  const D = window.APP_DATA;
  const isAdmin = currentUser.role === 'admin';
  const files = (caseData.attachments || []).filter(a => a.category === category);
  const [uploading, setUploading] = React.useState(false);
  const [dragOver, setDragOver] = React.useState(false);
  const [previewId, setPreviewId] = React.useState(null);
  const fileRef = React.useRef(null);
  /* freeeで作成した見積書の共有URLを「リンク・参照」に自動表示する。
     freee請求書APIにPDFのダウンロードendpointが無いため、ファイル添付ではなくリンクで持つ。
     この機能より前に作られた見積書には reportUrl が無いので、その時だけ1回サーバーに引き直させる。 */
  const fq = (category === '見積書' && caseData.freeeQuotation) ? caseData.freeeQuotation : null;
  React.useEffect(() => {
    if (!fq || !fq.id || fq.reportUrl) return;
    let alive = true;
    API.freeeQuotationLink(caseData.id)
      .then(r => { if (alive && r && r.reportUrl) patchCase(caseData.id, { freeeQuotation: { ...fq, reportUrl: r.reportUrl } }); })
      .catch(() => {}); // 取れなくても見積書タブは通常どおり使える
    return () => { alive = false; };
  }, [caseData.id, fq && fq.id, fq && fq.reportUrl]);
  const autoLinks = (fq && fq.reportUrl)
    ? [{ id: 'lk-freee', title: t('cd.freeeQuotationLink', { number: fq.number || '' }), url: fq.reportUrl, auto: true }] : [];
  const onFiles = async (fileList) => {
    const f = fileList && fileList[0];
    if (!f || uploading) return;
    if (f.size > 15 * 1024 * 1024) { showToast(t('case-detail.fileSizeLimit15MBAttach'), 'x'); return; }
    setUploading(true);
    try {
      const b64 = await new Promise((resolve, reject) => { const r = new FileReader(); r.onload = () => resolve(String(r.result).split(',')[1]); r.onerror = reject; r.readAsDataURL(f); });
      const r = await API.uploadAttachment({ caseId: caseData.id, name: f.name, mime: f.type || 'application/octet-stream', category: category, content: b64 });
      const patch = { attachments: [r.att, ...(caseData.attachments || [])] };
      if (!['review', 'submitted'].includes(caseData[statusField])) patch[statusField] = 'review';
      patchCase(caseData.id, patch);
      showToast(t('cd.toast.docFileAttached', { title, name: f.name }));
    } catch (e) { showToast(e.message, 'x'); }
    setUploading(false);
    if (fileRef.current) fileRef.current.value = '';
  };
  const removeFile = async (att) => {
    if (!window.confirm(t('common.confirmDelete'))) return;
    try { await API.deleteAttachment(att.id); patchCase(caseData.id, { attachments: (caseData.attachments || []).filter(x => x.id !== att.id) }); showToast(t('cd.toast.docDeleted', { title }), 'x'); }
    catch (e) { showToast(e.message, 'x'); }
  };
  const color = ATT_COLORS[category] || ATT_COLORS['その他'];
  return (
    <div style={{ padding: 22 }}>
      {analysis === 'proposal' && <ProposalBriefCard caseData={caseData} />}
      {analysis && <ProposalAnalysis caseData={caseData} kind={analysis} />}
      {analysis2 && <ProposalAnalysis caseData={caseData} kind={analysis2} />}
      {analysis === 'quote' && (
        <>
          <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 6 }}>
            <button onClick={() => navigate('quoteStudio', caseData.id)}
              style={{ fontSize: 12, fontWeight: 600, padding: '5px 10px', borderRadius: 8, border: '1px solid #d9d5f2', background: '#fff', color: '#4a5af0', cursor: 'pointer', fontFamily: 'inherit' }}>
              {t('cd.qs.openStudio')}
            </button>
          </div>
          <QuoteSheet caseData={caseData} />
        </>
      )}
      {/* 提出状況（ステータス＋提出期限） */}
      <div style={{ fontSize: 12.5, fontWeight: 700, color: '#1c1f26', marginBottom: 10 }}>{t('cd.docSubmissionStatus', { title })}</div>
      <div style={{ border: '1px solid #f0f1f4', borderRadius: 12, padding: '8px 16px', marginBottom: 24 }}>
        <DocStatusRow c={caseData} label={t('cd.submissionStatus')} statusField={statusField} ownerField={ownerField} editable={editable} />
        <EditableDateRow icon="clock" label={t('cd.submissionDue')} value={caseData[dueField]} onSave={(v) => saveCase({ id: caseData.id, [dueField]: v })} editable={editable}>
          {(() => {
            const due = dueField === 'due' ? caseDue(caseData) : (caseData[dueField] || null); const ddu = daysUntil(due);
            return due ? (
              <>
                <span style={{ fontWeight: 600, color: ddu !== null && ddu <= 3 && !['won', 'lost', 'done'].includes(caseData.status) ? '#dc2626' : '#2b2f38' }}>{fmtDateFull(due)}</span>
                {ddu !== null && !['won', 'lost', 'done'].includes(caseData.status) && <span style={{ fontSize: 12, color: '#9aa1ab', marginLeft: 8 }}>{ddu <= 0 ? t('cd.daysOverdue', { n: -ddu }) : t('cd.daysRemaining', { n: ddu })}</span>}
                {dueField === 'due' && !caseData.due && <span style={{ fontSize: 12, color: '#a8aeb8', marginLeft: 8 }}>{t('cd.dueAutoNote')}</span>}
              </>
            ) : <span style={{ color: '#9aa1ab' }}>{t('cd.undecided')}</span>;
          })()}
        </EditableDateRow>
      </div>

      {/* 提案書ファイル */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
        <Icon name="attach" size={15} stroke={2} style={{ color: '#7b828d', flex: '0 0 auto' }} />
        <span style={{ fontSize: 12.5, fontWeight: 700, color: '#1c1f26', whiteSpace: 'nowrap' }}>{t('cd.docFilesCount', { title, count: files.length })}</span>
        <span style={{ fontSize: 12, color: '#a8aeb8' }}>{t('cd.docFileFormats')}</span>
      </div>
      <input ref={fileRef} type="file" style={{ display: 'none' }} onChange={(e) => onFiles(e.target.files)} />
      {allowAdd && <div onClick={() => !uploading && fileRef.current && fileRef.current.click()}
        onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
        onDragLeave={() => setDragOver(false)}
        onDrop={(e) => { e.preventDefault(); setDragOver(false); onFiles(e.dataTransfer.files); }}
        style={{ border: '1.5px dashed ' + (dragOver ? '#4a5af0' : '#d8dce2'), background: dragOver ? '#f4f4fd' : 'transparent', borderRadius: 12, padding: '26px 0', textAlign: 'center', cursor: uploading ? 'wait' : 'pointer', transition: 'all .15s' }}>
        <Icon name={uploading ? 'refresh' : 'attach'} size={24} stroke={1.8} style={{ color: dragOver ? '#4a5af0' : '#b4bac3' }} />
        <div style={{ fontSize: 13, color: '#7b828d', marginTop: 9, fontWeight: 600 }}>{uploading ? t('cd.uploading') : t('cd.docDragDrop', { title })}</div>
        {!uploading && <div style={{ fontSize: 12, color: '#a8aeb8', marginTop: 4 }}>{t('cd.orText')}<span style={{ color: '#4a5af0', fontWeight: 600 }}>{t('btn.selectFile')}</span></div>}
      </div>}
      <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
        {files.map(att => {
          const by = D.user(att.uploadedBy);
          const pk = attPreviewKind(att);
          const open = previewId === att.id;
          return (
            <React.Fragment key={att.id}>
            <div className="row-hover" style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '10px 12px', border: '1px solid #f0f1f4', borderRadius: 9 }}>
              <div style={{ width: 32, height: 32, borderRadius: 7, background: color + '15', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto' }}>
                <Icon name="attach" size={15} stroke={2} style={{ color }} />
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <a href={API.attachmentUrl(att.id)} target="_blank" rel="noreferrer"
                  style={{ fontSize: 13.5, fontWeight: 600, color: '#2b2f38', textDecoration: 'none', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', display: 'block' }}>{att.name}</a>
                <div style={{ fontSize: 12, color: '#9aa1ab', marginTop: 2 }}>{fmtSize(att.size)} · <span style={{ color: '#4a5af0' }}>{fmtDate(att.at, true)} {fmtTime(att.at)}</span>{by ? ` · ${by.short}` : ''}</div>
              </div>
              {pk && <IconButton name="eye" size={15} active={open} title={t('cd.preview')} onClick={() => setPreviewId(open ? null : att.id)} />}
              <a href={API.attachmentUrl(att.id)} target="_blank" rel="noreferrer"><IconButton name="download" size={15} title={t('btn.download')} /></a>
              {isAdmin && <IconButton name="x" size={15} title={t('btn.deleteAdminOnly')} onClick={() => removeFile(att)} />}
            </div>
            {open && pk && <PreviewFrame kind={pk} src={API.attachmentUrl(att.id)} />}
            </React.Fragment>
          );
        })}
        {files.length === 0 && <div style={{ padding: '18px 0', textAlign: 'center', color: '#b4bac3', fontSize: 12.5 }}>{t('cd.docNotUploaded', { title })}</div>}
      </div>

      {/* リンク・参照（Google Drive / Notion 等の URL を貼る） */}
      <div style={{ marginTop: 24 }}>
        <RefLinksSection caseData={caseData} field={linksField} autoLinks={autoLinks} hint={t('cd.refLinksHintDoc')} editable={allowAdd} autoStatusField={statusField} />
      </div>
    </div>
  );
}

/* 提案書・見積書の提出ステータス行（未着手/準備中/提出済み/不要 ＋ 書類担当）— メンバーも変更可 */
Object.assign(window, { FeatureList, ProposalAnalysis, DocTab });
