// CaseP1 — P1機能：レコード追加／帳票（作成→発行）／完了クローズ／現場モード
// 導線：一覧の末尾に「＋追加」・工程の消込モーダルから帳票・完了段階でクローズ判定。

/* ＋追加（稼働・工事・保険・箇所）— 一覧の末尾から起票する
   K18-49: 池田さん版に合わせ、見出しの下の補足文(sub)は持たない。 */
const P1_KINDS = {
  visit: { label:"調査（稼働）", steps:["受注","日程調整","実施","報告","見積","社内承認","請求","入金","完了"] },
  work:  { label:"工事", steps:["見積","社内承認","受注","日程調整","実施","報告","請求","入金","完了"] },
  ins:   { label:"保険", steps:["受付","申請","鑑定","認定","保険金請求","入金"] },
  spot:  { label:"箇所（部屋）" },
  party: { label:"関係者" },
};

function P1Field({ label, children, hint }) {
  return (
    <div>
      <div style={{ fontSize:"var(--text-caption)", fontWeight:600, letterSpacing:"0.06em", color:"var(--muted-foreground)", marginBottom:5 }}>{label}</div>
      {children}
      {hint && <div style={{ fontSize:"var(--text-caption)", color:"var(--foreground-subtle)", marginTop:4 }}>{hint}</div>}
    </div>
  );
}
function P1Pick({ items, value, onChange }) {
  return (
    <div style={{ display:"flex", gap:5, flexWrap:"wrap" }}>
      {items.map(([v,l])=>(
        <button key={v} type="button" aria-pressed={value===v} onClick={()=>onChange(v)} style={{ all:"unset", cursor:"pointer", fontFamily:"var(--font-sans)",
          fontSize:"var(--text-body-sm)", fontWeight:value===v?700:500, padding:"5px 11px", borderRadius:"var(--radius-full)", whiteSpace:"nowrap",
          border:"1px solid "+(value===v?"var(--brand)":"var(--border)"),
          background:value===v?"var(--brand-muted)":"var(--card)", color:value===v?"var(--brand)":"var(--muted-foreground)" }}>{l}</button>
      ))}
    </div>
  );
}

/* K18-12(0109): 工事の区分。値は DB の work_category(track.create の workCategory・rpc_seed の works[].kind)。
   追加したあとで区分は変えられない(ルートが変わるため ─ K18-73)。 */
const P1_WORK_KINDS = [["原因箇所工事","原因箇所工事（原因元）"],["被害箇所工事","被害箇所工事（復旧）"]];
const P1_WORK_KIND_STANCE = { "原因箇所工事":"原因", "被害箇所工事":"被害" };
/* H2(9/25): 工事の精算方式と進め方。工程の型(route_templates)は (区分, 精算方式, 進め方) で決まり、型が無いと
   工程 0 本の工事になる(ほかの端末で案件詳細が真っ白になっていた)。追加するときに選ばせ、型の無い組み合わせでは
   作らない(判定は store.jsx workRouteGap。「未定」は選択肢に出さない ─ 型が無いため)。 */
const P1_WORK_SETTLEMENTS = [["自費精算","自費精算"],["保険精算","保険精算"],["工事内包","工事内包"],["自社負担","自社負担"]];
const p1ApproachItems = () => ["P1","P2","P3"].map(a => [a, window.afApproachLabel ? window.afApproachLabel(a) : a]);

function AddRecordModal({ kind, rooms, spots, workDefaults, onClose, onSave }) {
  const K = P1_KINDS[kind];
  // K18-12: 工事の号室の初期値は、選んだ区分と同じ立場の部屋(原因箇所工事→原因の部屋)の先頭、
  // 無ければ「両方」の部屋。サーバ(0109)が号室を結び付けるときに探す順と同じ。
  // spots を渡されない呼び出し元は従来どおり rooms の先頭。
  const roomFor = wk => {
    const st = P1_WORK_KIND_STANCE[wk];
    const ok = s => s && s.room && s.room!=="—";
    const hit = (spots||[]).find(s => ok(s) && s.kind===st) || (spots||[]).find(s => ok(s) && s.kind==="両方");
    return hit ? hit.room : "";
  };
  const [workKind, setWorkKind] = React.useState("被害箇所工事");
  const [room, setRoom] = React.useState(() => (kind==="work" && spots) ? roomFor("被害箇所工事") : ((rooms&&rooms[0])||""));
  const [roomTouched, setRoomTouched] = React.useState(false);
  const pickWorkKind = wk => { setWorkKind(wk); if(!roomTouched && spots) setRoom(roomFor(wk)); };
  const [a, setA] = React.useState("");
  const [b, setB] = React.useState("");
  const [spotKind, setSpotKind] = React.useState("被害");
  // 役割の語彙は DB の relation_role enum に合わせる(案件詳細-9。0001_enums.sql)。
  // 「業者」「保険」だった従来の見た目上のラベルは、保存後の再読込でも同じ文字が
  // 出るように enum の言い回し(施工会社/保険会社)へ寄せた。
  const [role, setRole] = React.useState("居住者");
  const [policy, setPolicy] = React.useState("個人賠償責任保険");
  // 書き込み経路-5(0062): 保険会社は組織マスタから検索して選ぶ(名前だけでは
  // insurer_org_id に解決できない ─ Intake.jsxの取引先ピッカーと同じ理由)。
  // 証券番号(policyNo)は自由入力の新設欄。
  const [insurerDbId, setInsurerDbId] = React.useState(null);
  const [insurerHits, setInsurerHits] = React.useState(null); // null=未検索、[]=0件
  const [insurerErr, setInsurerErr] = React.useState("");
  const [policyNo, setPolicyNo] = React.useState("");
  React.useEffect(() => {
    if (kind !== "ins") return undefined;
    const q = a.trim();
    if (!q) { setInsurerHits(null); setInsurerErr(""); return undefined; }
    let alive = true;
    const t = setTimeout(() => {
      const fetcher = window.payPartnerFetch;
      const done = (rows, err) => { if (alive) { setInsurerHits(rows); setInsurerErr(err || ""); } };
      if (typeof fetcher === "function") {
        fetcher(q, 20).then(r => done((r && r.rows) || [], r && r.offline ? "取引先マスタ未取得" : ""))
          .catch(e => done([], (e && e.message) || "取引先を検索できませんでした"));
      } else {
        done([], "取引先マスタ未取得");
      }
    }, 250);
    return () => { alive = false; clearTimeout(t); };
  }, [a, kind]);
  const pickInsurer = (org) => { setA(org.name); setInsurerDbId(org.id); setInsurerHits(null); };
  // H2: 工事の精算方式・進め方。初期値は依頼者の規定(名簿の控え)。規定が無ければ選ばれていない状態から始める。
  const wd = workDefaults || {};
  const routeSettles = window.WORK_ROUTE_SETTLEMENTS || P1_WORK_SETTLEMENTS.map(x=>x[0]);
  const [settle, setSettle] = React.useState(() => routeSettles.indexOf(wd.settlement) >= 0 ? wd.settlement : "");
  const [approachPicked, setApproachPicked] = React.useState(null);   // 人が選んだ進め方(選ぶまでは初期値に従う)
  const [routeTried, setRouteTried] = React.useState(false);          // 「追加」を押して止めたか
  const apprDefault = kind==="work" && window.workApproachDefault
    ? window.workApproachDefault({ spots }, workKind, room, settle, wd.approach) : { approach:null, why:null, rooms:[] };
  const approach = settle==="保険精算" ? (approachPicked || apprDefault.approach || null) : null;
  const routeGap = kind==="work" && window.workRouteGap ? window.workRouteGap(settle, approach) : null;
  const ok = kind==="ins" ? a && b : kind==="party" ? a : kind==="spot" ? room && a : a;
  const save = () => {
    if (routeGap) { setRouteTried(true); return; }
    if (!ok) return;
    onSave({ kind, room, a, b, spotKind, role, policy, insurerDbId, policyNo: policyNo.trim() || null,
      settlement: kind==="work" ? settle : undefined, approach: kind==="work" ? approach : undefined, workKind });
  };
  const hintWarn = t => <span style={{ color:"var(--warning)", fontWeight:600 }}>{t}</span>;
  const settleHint = wd.settlement && routeSettles.indexOf(wd.settlement) >= 0
    ? "依頼者の規定は「"+wd.settlement+"」です。この工事だけ変えることもできます"
    : settle
      ? "依頼者に使える規定が無いので、選んだ「"+settle+"」で作ります"
      : wd.settlement === "未定"
        ? hintWarn("依頼者の規定は「未定」です。工程の型が決まらないので、精算方式を選んでください")
        : hintWarn("依頼者に規定の精算方式がありません。選んでから追加してください（選ばないと工程が作れません）");
  const apprLabel = a0 => (window.afApproachLabel ? window.afApproachLabel(a0) : a0);
  const approachHint = approachPicked
    ? (apprDefault.approach && approachPicked !== apprDefault.approach ? "初期値（"+apprLabel(apprDefault.approach)+"）から変えています" : null)
    : apprDefault.why === "unlivable"
      ? "住めない部屋（"+apprDefault.rooms.join("・")+"）があるので P1（先行工事）にしています"
      : apprDefault.why === "requester"
        ? "依頼者の規定は「"+apprLabel(apprDefault.approach)+"」です"
        : hintWarn("依頼者に進め方の規定がありません。進め方を選んでください");
  return (
    <ScModal title={K.label+"を追加"} onClose={onClose}
      footer={<React.Fragment>
        <AfButton onClick={onClose}>キャンセル</AfButton>
        <AfButton variant="primary" onClick={save}>追加</AfButton>
      </React.Fragment>}>
      {kind==="visit" && <React.Fragment>
        <P1Field label="調査業者"><AfInput value={a} onChange={e=>setA(e.target.value)} placeholder="例：及川設備" style={{ width:"100%" }} /></P1Field>
        <P1Field label="対応する箇所" hint="複数室は「402・302」のように並べる"><AfInput value={b} onChange={e=>setB(e.target.value)} placeholder="例：302" style={{ width:"100%" }} /></P1Field>
      </React.Fragment>}
      {kind==="work" && <React.Fragment>
        <P1Field label="区分" hint="原因箇所工事＝漏水の原因元の修理、被害箇所工事＝被害を受けた部屋の復旧。追加したあとで区分は変えられません">
          <P1Pick items={P1_WORK_KINDS} value={workKind} onChange={pickWorkKind} />
        </P1Field>
        <P1Field label="号室" hint={"案件の対象部屋に結び付けます。対象部屋に無い号室を書くと、その部屋を"+P1_WORK_KIND_STANCE[workKind]+"の部屋として足します。空なら案件全体"}>
          <AfInput mono value={room} onChange={e=>{ setRoomTouched(true); setRoom(e.target.value); }} placeholder="例：202" style={{ width:120 }} /></P1Field>
        <div data-work-settle={settle||""}>
          <P1Field label="精算方式" hint={settleHint}>
            <P1Pick items={P1_WORK_SETTLEMENTS} value={settle} onChange={v=>{ setSettle(v); setRouteTried(false); }} />
          </P1Field>
        </div>
        {settle==="保険精算" && <div data-work-approach={approach||""}>
          <P1Field label="進め方" hint={approachHint}>
            <P1Pick items={p1ApproachItems()} value={approach} onChange={v=>{ setApproachPicked(v); setRouteTried(false); }} />
          </P1Field>
        </div>}
        <P1Field label="工事内容"><AfInput value={a} onChange={e=>setA(e.target.value)} placeholder="例：クロス張替" style={{ width:"100%" }} /></P1Field>
        <P1Field label="原因箇所" hint="どの原因から生じた工事か"><AfInput value={b} onChange={e=>setB(e.target.value)} placeholder="例：廊下の天井" style={{ width:"100%" }} /></P1Field>
        {routeTried && routeGap && <div data-work-route-block={routeGap.need} role="alert">
          <AfAlert variant="error" title="この組み合わせでは工事を作れません">
            {routeGap.reason}精算方式（保険精算なら進め方も）ごとに工程の並びが決まっていて、合うものが無いと工程が 1 つも無い工事になり、進められません。
          </AfAlert>
        </div>}
      </React.Fragment>}
      {kind==="ins" && <React.Fragment>
        <P1Field label="保険会社" hint="名前で検索して選ぶ。候補に無ければそのまま自由入力できます">
          <AfInput value={a} onChange={e=>{ setA(e.target.value); setInsurerDbId(null); }} placeholder="例：日新火災" style={{ width:"100%" }} />
          {insurerDbId
            ? <div style={{ fontSize:"var(--text-caption)", color:"var(--success)", marginTop:4 }}>取引先マスタと一致</div>
            : <React.Fragment>
                {insurerHits && insurerHits.length > 0 && (
                  <div style={{ display:"flex", flexDirection:"column", gap:2, border:"1px solid var(--border)", borderRadius:"var(--radius-md)", padding:4, marginTop:4, maxHeight:140, overflowY:"auto" }}>
                    {insurerHits.map(o => (
                      <button key={o.id} type="button" onClick={()=>pickInsurer(o)} style={{
                        all:"unset", cursor:"pointer", padding:"6px 8px", borderRadius:"var(--radius-sm)", fontFamily:"var(--font-sans)" }}
                        onMouseEnter={e=>e.currentTarget.style.background='var(--accent)'}
                        onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                        <span style={{ fontWeight:600, fontSize:"var(--text-body-sm)" }}>{o.name}</span>
                      </button>
                    ))}
                  </div>
                )}
                {insurerErr && <div style={{ fontSize:"var(--text-caption)", color:"var(--warning)", marginTop:4 }}>{insurerErr}(このまま自由入力として保存できます)</div>}
                {!insurerErr && insurerHits && insurerHits.length === 0 && (
                  <div style={{ fontSize:"var(--text-caption)", color:"var(--foreground-subtle)", marginTop:4 }}>一致する取引先が見つかりません(このまま自由入力として保存できます)</div>
                )}
              </React.Fragment>}
        </P1Field>
        <P1Field label="契約者"><AfInput value={b} onChange={e=>setB(e.target.value)} placeholder="例：コスモ管理（建物）" style={{ width:"100%" }} /></P1Field>
        <P1Field label="証券番号" hint="任意・自由入力"><AfInput value={policyNo} onChange={e=>setPolicyNo(e.target.value)} placeholder="例：AB-2026-00123" style={{ width:"100%" }} /></P1Field>
        <P1Field label="保険種類" hint="加害側＝個人賠償／賠償責任、被害側＝火災保険・施設賠償など">
          <P1Pick items={[["個人賠償責任保険","個人賠償責任"],["施設賠償責任保険","施設賠償責任"],["火災保険","火災保険"],["賠償責任保険","賠償責任"]]} value={policy} onChange={setPolicy} />
        </P1Field>
      </React.Fragment>}
      {kind==="spot" && <React.Fragment>
        <P1Field label="立場"><P1Pick items={[["原因","原因"],["被害","被害"]]} value={spotKind} onChange={setSpotKind} /></P1Field>
        <P1Field label="号室"><AfInput mono value={room} onChange={e=>setRoom(e.target.value)} placeholder="例：202" style={{ width:120 }} /></P1Field>
        <P1Field label="箇所"><AfInput value={a} onChange={e=>setA(e.target.value)} placeholder="例：洋室の壁" style={{ width:"100%" }} /></P1Field>
        <P1Field label="持ち主・窓口"><AfInput value={b} onChange={e=>setB(e.target.value)} placeholder="例：コスモ管理" style={{ width:"100%" }} /></P1Field>
      </React.Fragment>}
      {kind==="party" && <React.Fragment>
        <P1Field label="立場"><P1Pick items={[["居住者","居住者"],["管理会社","管理会社"],["オーナー","オーナー"],["施工会社","施工会社"],["保険会社","保険会社"]]} value={role} onChange={setRole} /></P1Field>
        <P1Field label="名前"><AfInput value={a} onChange={e=>setA(e.target.value)} placeholder="例：田中様（202入居者）" style={{ width:"100%" }} /></P1Field>
      </React.Fragment>}
      {/* 実際の工程はDBのroute_steps(取引先の規定精算方式・進め方から選ばれるルート)で決まり、
          ここでは選べない値(精算方式・進め方)まで含めて決まるため、保存前には確定しない。
          目安として典型的な流れだけ示す(supabase/migrations/0025_track_create.sql)。
          このプレビューの工程名の並びはDBのルートからはまだ作っていません(保存後に
          route_stepsから作られたものへ差し替わる ─ CaseDetailV25.jsxのstepId補充と同じ経路)。 */}
      {K.steps && <AfAlert variant="info" title="工程の目安（典型的な流れ）">
        {K.steps.join(" → ")}
        <div style={{ marginTop:4, fontSize:"var(--text-caption)", color:"var(--muted-foreground)" }}>
          この並びはDBのルートからはまだ作っていません。保存すると実際の工程に差し替わります。
        </div>
      </AfAlert>}
    </ScModal>
  );
}

/* 決-8: 報告書・見積はまるろうOSで作らない。モーダルは「いつ・誰に・何を」の記録だけ。
   本文入力欄は廃止(書いて送られたと誤解されるため)。PDF生成は永久に作らない。請求は frees(決-4)。 */
const P1_DOCS = {
  報告書: { title:"調査報告書", step:"報告" },
  見積書: { title:"見積書", step:"見積" },
  請求書: { title:"請求書", step:"請求" },
};

function DocModal({ type, d, onClose, onIssue }) {
  const T = P1_DOCS[type] || P1_DOCS.報告書;
  const [to, setTo] = React.useState(d.client || "");
  const isInvoice = type === "請求書";
  return (
    <ScModal width="var(--modal-w-md)" title={T.title+"の発行を記録"} onClose={onClose}
      footer={<React.Fragment>
        <AfButton onClick={onClose}>キャンセル</AfButton>
        <AfButton onClick={()=>onIssue({ type, name:T.title, to, body:"", advance:false })}>下書きとして記録</AfButton>
        <AfButton variant="primary" onClick={()=>onIssue({ type, name:T.title, to, body:"", advance:true })}>発行を記録して工程を進める</AfButton>
      </React.Fragment>}>
      {/* K18-49: 見出しの下の補足文は外し、決-8 の断り(何をしない画面か)はこの 1 か所にまとめた。 */}
      <AfAlert variant="notice" title="本文は入力しません">
        {isInvoice
          ? "請求書の作成・送付は freee です。ここは発行の記録と工程の進行だけで、本文・PDF・メール送付は行いません。"
          : "報告書・見積は社内で作って送付します。ここは「いつ・誰に・何を」の記録だけです。PDFは作りません。"}
      </AfAlert>
      <P1Field label="宛先（記録）"><AfInput value={to} onChange={e=>setTo(e.target.value)} placeholder="依頼者・管理会社など" style={{ width:240 }} /></P1Field>
    </ScModal>
  );
}

/* 完了：クローズ条件の判定(案件を閉じる操作は今は出さない)
   H2 の検証(9/25): 保険を配列として読むように直したので、ここは初めて最後まで描けるようになった。
   直す前は保険の無い案件(rpc_seed の ins:[] は真)で ins.steps を読んで必ず落ちていたので、本番では
   「案件を完了にする」に一度も届いていない。そのボタンは確認なしで cases.closed_at を書き(CaseDetailV25 の
   onClose → mstore-diff の closedFlag)、閉じた案件は読み込み(未完了だけ)から消え、開き直す画面も無い。
   H2 の直しのついでに、元に戻せない書き込みを新しく使えるようにはしない ─ 条件の確かめだけを出す。
   閉じる操作を出すのは施主の判断で、確認を 1 回挟む・再オープンの経路・閉じる操作の E2E(closed_at と一覧)を
   揃えてから。onClose は受け取るが、ここでは呼ばない(tests/ui/work-no-route.test.js が固定)。 */
function P1Complete({ d, closed, onClose, onToast }) {
  // H2: 工程が「無い」(rpc_seed が null で返す)トラックでも落ちない。工程が 1 つも無いのは完了ではない。
  // 保険は配列(rpc_seed は ins:[]・複数ありうる)。前は d.ins をトラック 1 本とみなして ins.steps を読み、
  // 保険の無い案件([] は真)でも「完了」を開くと落ちていた。
  const stepsOf = t => (t && Array.isArray(t.steps)) ? t.steps : [];
  const allDone = t => { const s = stepsOf(t); return s.length>0 && s.every(x=>x.s==="done"||x.s==="skip"); };
  const insList = !d.ins ? [] : (Array.isArray(d.ins) ? d.ins : [d.ins]);
  const conds = [
    { label:"全工事が完了している", ok:d.works.length>0 && d.works.every(allDone), why:d.works.filter(w=>!allDone(w)).map(w=>w.room+" "+w.work).join("・") },
    { label:"全調査が完了している", ok:d.visits.length>0 && d.visits.every(allDone), why:d.visits.filter(v=>!allDone(v)).map(v=>v.vendor).join("・") },
    { label:"保険が決着している", ok:insList.every(allDone), why:insList.length ? insList.filter(i=>!allDone(i)).map(i=>i.status||i.name||"保険").join("・") : "保険なし" },
    { label:"入金が全て確認済み", ok:[...d.works,...d.visits].every(t=>{ const p=stepsOf(t).find(s=>s.n==="入金"); return !p || p.s==="done"; }), why:"未入金の進行あり" },
  ];
  const ready = conds.every(c=>c.ok);
  return (
    <div>
      <div style={{ display:"flex", flexDirection:"column", gap:7, marginBottom:12 }}>
        {conds.map((c,i)=>(
          <div key={i} style={{ display:"flex", alignItems:"center", gap:9, flexWrap:"wrap" }}>
            <span style={{ width:15, height:15, borderRadius:3, boxSizing:"border-box", flexShrink:0,
              border:"1.5px solid "+(c.ok?"var(--success)":"var(--border-strong)"), background:c.ok?"var(--success)":"transparent",
              display:"inline-flex", alignItems:"center", justifyContent:"center" }}>
              {c.ok && <span style={{ fontSize:9, color:"#fff", fontWeight:700, lineHeight:1 }}>✓</span>}
            </span>
            <span style={{ fontSize:"var(--text-body-md)", fontWeight:c.ok?400:600, color:c.ok?"var(--muted-foreground)":"var(--foreground)" }}>{c.label}</span>
            {!c.ok && c.why && <span style={{ fontSize:"var(--text-caption)", color:"var(--warning)" }}>残り：{c.why}</span>}
          </div>
        ))}
      </div>
      {closed
        ? <AfAlert variant="success" title="案件完了">クローズ済み。以降の変更は再オープンしてから行います。</AfAlert>
        : <div data-case-close="unavailable" data-case-close-ready={ready?"1":"0"}
            /* 進行表は 390 幅では横に流れる(min-width 712px)ので、文は画面の幅で折り返す(右が切れて読めない)。 */
            style={{ fontSize:"var(--text-body-md)", lineHeight:1.6, color:"var(--foreground-subtle)", maxWidth:"min(100%, calc(100vw - 96px))" }}>
            {ready ? "条件は揃っています。" : "条件の揃い具合をここで確かめられます（工程の消込で自動判定）。"}
            案件を閉じる操作はまだ使えません（閉じた案件を開き直す手段ができてから出します）。
          </div>}
    </div>
  );
}

/* ===== P1'-6 選択の入口 ===== */
const P1_SETTLE = [
  { v:"自費", label:"自費", note:"依頼者・原因者へ直接請求" },
  { v:"保険連動", label:"保険連動", note:"保険の認定額を充当し、残りを自費で請求" },
  { v:"保険充当", label:"保険充当", note:"全額を保険から回収" },
];
const P1_UNITS = [
  { v:"月次窓口精算", label:"月次窓口精算", note:"取引先単位で当月分をまとめて1通" },
  { v:"月次案件精算", label:"月次案件精算", note:"案件ごとに請求" },
  { v:"都度精算", label:"都度精算", note:"工程完了ごとに請求" },
];
const p1Vendors = () => (window.PV_ROWS||[]).filter(x=>x.kind==="業者").map(x=>({ v:x.name, label:x.name, note:x.type+"・関連 "+x.cases+"件" }));
const p1Payers = d => {
  const out = [];
  const push = (name, note) => { if(name && !out.some(x=>x.v===name)) out.push({ v:name, label:name, note }); };
  push(d.client, "依頼者");
  (d.spots||[]).forEach(s=>push(s.owner, s.kind+"箇所の持ち主"));
  if(d.ins) push(d.ins.name, "保険会社");
  (window.PV_ROWS||[]).filter(x=>x.kind!=="業者").forEach(x=>push(x.name, x.type));
  return out;
};
const p1Targets = d => {
  const out = [];
  (d.works||[]).forEach(w=>{
    out.push({ v:w.id, label:w.room+" "+w.work, note:"工事全体 "+yen(w.detail.billed) });
    (w.detail.items||[]).forEach(it=>out.push({ v:w.id+":"+it.id, label:w.room+" "+it.name, note:it.vendor+"・"+yen(it.amt) }));
  });
  return out;
};

/* 選択モーダル（単一選択・任意で自由入力を許す）。K18-49: 見出しの下の補足文は持たない。 */
function PickModal({ title, options, value, multi, allowFree, freeLabel, onClose, onSave }) {
  const [sel, setSel] = React.useState(multi ? (Array.isArray(value)?value:[]) : value);
  const [free, setFree] = React.useState("");
  const on = v => multi ? sel.indexOf(v)>=0 : sel===v;
  const pick = v => multi ? setSel(l=>l.indexOf(v)>=0 ? l.filter(x=>x!==v) : [...l, v]) : setSel(v);
  return (
    <ScModal title={title} onClose={onClose}
      footer={<React.Fragment>
        <AfButton onClick={onClose}>キャンセル</AfButton>
        <AfButton variant="primary" onClick={()=>onSave(free.trim() ? (multi?[...sel, free.trim()]:free.trim()) : sel)}>確定</AfButton>
      </React.Fragment>}>
      <div style={{ display:"flex", flexDirection:"column", gap:5 }}>
        {options.map(o=>(
          <button key={o.v} onClick={()=>pick(o.v)} style={{ all:"unset", boxSizing:"border-box", width:"100%", cursor:"pointer",
            display:"flex", alignItems:"center", gap:9, padding:"9px 11px", borderRadius:"var(--radius-md)", fontFamily:"var(--font-sans)",
            border:"1px solid "+(on(o.v)?"var(--brand)":"var(--border)"), background:on(o.v)?"var(--brand-muted)":"var(--card)",
            transition:"all var(--duration-fast) var(--easing)" }}>
            <span style={{ width:14, height:14, borderRadius:multi?3:999, boxSizing:"border-box", flexShrink:0,
              border:"1.5px solid "+(on(o.v)?"var(--brand)":"var(--border-strong)"), background:on(o.v)?"var(--brand)":"transparent",
              display:"inline-flex", alignItems:"center", justifyContent:"center" }}>
              {on(o.v) && <span style={{ fontSize:8, color:"#fff", fontWeight:700, lineHeight:1 }}>✓</span>}
            </span>
            <span style={{ fontSize:"var(--text-body-md)", fontWeight:600, color:on(o.v)?"var(--brand)":"var(--foreground)" }}>{o.label}</span>
            {o.note && <span style={{ marginLeft:"auto", fontSize:"var(--text-caption)", color:"var(--foreground-subtle)", textAlign:"right" }}>{o.note}</span>}
          </button>
        ))}
        {options.length===0 && <div style={{ fontSize:"var(--text-body-md)", color:"var(--foreground-subtle)" }}>選べる候補がありません。下に直接入力してください。</div>}
      </div>
      {allowFree && <P1Field label={freeLabel||"一覧に無い場合は入力"}>
        <AfInput value={free} onChange={e=>setFree(e.target.value)} placeholder="名称を入力" style={{ width:"100%" }} />
      </P1Field>}
    </ScModal>
  );
}

/* 値をタップして選び直せるボタン（未設定は「選択」と出す） */
function P1Pickable({ value, placeholder, onClick, tone }) {
  const [hov, setHov] = React.useState(false);
  const empty = !value;
  return (
    <button onClick={onClick} onMouseEnter={()=>setHov(true)} onMouseLeave={()=>setHov(false)}
      style={{ all:"unset", cursor:"pointer", fontFamily:"var(--font-sans)", display:"inline-flex", alignItems:"center", gap:4,
        fontSize:"var(--text-body-sm)", fontWeight:600, padding:"1px 7px", borderRadius:"var(--radius-sm)",
        color:empty?"var(--brand)":(tone||"var(--foreground)"),
        background:hov?"var(--brand-muted)":"transparent",
        border:"1px dashed "+(hov?"var(--brand-border)":"transparent") }}>
      {empty ? (placeholder||"選択") : value}
      <span style={{ fontSize:8, color:"var(--foreground-subtle)" }}>▾</span>
    </button>
  );
}

Object.assign(window, { AddRecordModal, DocModal, P1Complete, P1_DOCS, P1_KINDS, P1_WORK_KINDS, P1Field, PickModal, P1Pickable,
  P1_SETTLE, P1_UNITS, p1Vendors, p1Payers, p1Targets });
