// child-rearing-allowance-web — 固有ロジック（共通coreへ委譲される per-system handle）
import { join } from "node:path";

// ── 児童扶養手当 手当額算定（標準仕様書: 所得により全部支給/一部支給/支給停止を算定）──
//   金額・係数・所得制限限度額は政令パラメータ（既定は令和6年4月〜の標準値）。
//   所得は各種控除後の額。支給対象児童=18歳到達後最初の3/31まで（障害児は20歳未満）。
const CRA_RATES = {
  fullLimitBase: 490000,      // 全部支給 所得制限限度額（扶養親族0人）
  partialLimitBase: 1920000,  // 一部支給 所得制限限度額（扶養親族0人）
  perDependent: 380000,       // 扶養親族1人ごとの加算
  full: { first: 45500, second: 10750, third: 6450 }, // 全部支給 月額（1人目/2人目加算/3人目以降加算）
  // 一部支給: 額 = base − (所得 − 全部支給限度額) × coef（10円未満四捨五入、[min,max]にクランプ）
  partial: {
    first: { base: 45490, coef: 0.0243007, min: 10740, max: 45490 },
    second: { base: 10740, coef: 0.0037483, min: 5380, max: 10740 },
    third: { base: 6440, coef: 0.0022448, min: 3230, max: 6440 },
  },
};
function craFiscalYearEndAfterAge(birthDate, age) {
  const b = new Date(birthDate);
  const reachYear = b.getFullYear() + age;
  const yearEnd = b.getMonth() <= 2 ? reachYear : reachYear + 1;
  return new Date(yearEnd, 2, 31, 23, 59, 59, 999);
}
const craRound10 = (x) => Math.round(x / 10) * 10;
function computeChildRearingAllowance({ incomeYen, dependents = 0, childCount = 1 }, rates = CRA_RATES) {
  const income = Number(incomeYen) || 0;
  const dep = Math.max(0, Number(dependents) || 0);
  const n = Math.max(0, Number(childCount) || 0);
  const fullLimit = rates.fullLimitBase + rates.perDependent * dep;
  const partialLimit = rates.partialLimitBase + rates.perDependent * dep;
  if (n === 0) return { type: "none", monthly: 0, reason: "支給対象児童なし", fullLimit, partialLimit };
  if (income <= fullLimit) {
    const monthly = rates.full.first + (n >= 2 ? rates.full.second : 0) + (n >= 3 ? rates.full.third * (n - 2) : 0);
    return { type: "full", monthly, fullLimit, partialLimit, childCount: n };
  }
  if (income <= partialLimit) {
    const over = income - fullLimit;
    const calc = (p) => Math.min(p.max, Math.max(p.min, craRound10(p.base - over * p.coef)));
    let monthly = calc(rates.partial.first);
    if (n >= 2) monthly += calc(rates.partial.second);
    if (n >= 3) monthly += calc(rates.partial.third) * (n - 2);
    return { type: "partial", monthly, fullLimit, partialLimit, childCount: n };
  }
  return { type: "none", monthly: 0, reason: "所得が一部支給限度額を超過（支給停止）", fullLimit, partialLimit };
}

export async function handle(ctx) {
  const { req, res, pathname, searchParams, actor, send, sendRaw, parseBody, readJson, writeJson, audit,
    maskRecord, validateRecord, requireRole, hasRole, dataRoot, restrictionsFile, crypto,
    dbSelect, dbSelectOne, dbInsert, dbUpdate, dbDelete } = ctx;
  const reply = (...a) => { send(...a); return true; };
  const craReceiversFile = join(dataRoot, "cra-receivers.json");
  const craChildrenFile = join(dataRoot, "cra-children.json");
  const craPaymentsFile = join(dataRoot, "cra-payments.json");


    // ── 児童手当 受給者 (CRA Receivers) ──────────────────────────────────

    if (pathname === "/api/cra-receivers" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const receivers = (await dbSelect("child_rearing.cra_receiver")) ?? await readJson(craReceiversFile, []);
      const restrictions = await readJson(restrictionsFile, []);
      return reply(res, 200, receivers.map((r) => maskRecord(actor, restrictions, r)));
    }

    if (pathname === "/api/cra-receivers" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.fullName || !body.address) return reply(res, 400, { error: "fullName, address required" });
      const receivers = (await dbSelect("child_rearing.cra_receiver")) ?? await readJson(craReceiversFile, []);
      const applicationNumber = "JT-" + Date.now().toString(36).toUpperCase();
      const receiver = { id: crypto.randomUUID(), applicationNumber, fullName: body.fullName, address: body.address, income: body.income ?? null, certificationStatus: "pending", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("child_rearing.cra_receiver", receiver); if (dbInserted === null) { receivers.push(receiver); await writeJson(craReceiversFile, receivers); }
      audit(req, "cra-receiver.apply", { id: receiver.id, applicationNumber });
      return reply(res, 201, receiver);
    }

    const craReceiverMatch = pathname.match(/^\/api\/cra-receivers\/([^/]+)$/);
    if (craReceiverMatch) {
      requireRole(actor, "CLERK");
      const receiver = (await dbSelectOne("child_rearing.cra_receiver", craReceiverMatch[1]))
        ?? (await readJson(craReceiversFile, [])).find((r) => r.id === craReceiverMatch[1]);
      if (!receiver) return reply(res, 404, { error: "receiver not found" });
      if (req.method === "GET") return reply(res, 200, receiver);
      if (req.method === "PUT") {
        const body = await parseBody(req);
        Object.assign(receiver, body, { id: receiver.id, applicationNumber: receiver.applicationNumber, updatedAt: new Date().toISOString() });
        const upd = await dbUpdate("child_rearing.cra_receiver", receiver.id, receiver); if (upd === null) { const wl = await readJson(craReceiversFile, []); const wi = wl.findIndex((x) => x.id === receiver.id); if (wi !== -1) { wl[wi] = receiver; } await writeJson(craReceiversFile, wl); }
        audit(req, "cra-receiver.update", { id: receiver.id });
        return reply(res, 200, receiver);
      }
    }

    const craReceiverCertifyMatch = pathname.match(/^\/api\/cra-receivers\/([^/]+)\/certify$/);
    if (craReceiverCertifyMatch && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const receiver = (await dbSelectOne("child_rearing.cra_receiver", craReceiverCertifyMatch[1]))
        ?? (await readJson(craReceiversFile, [])).find((r) => r.id === craReceiverCertifyMatch[1]);
      if (!receiver) return reply(res, 404, { error: "receiver not found" });
      if (receiver.certificationStatus !== "pending") return reply(res, 409, { error: "receiver is not in pending status" });
      const body = await parseBody(req);
      if (!["certified", "rejected"].includes(body.decision)) return reply(res, 400, { error: "decision must be certified or rejected" });
      receiver.certificationStatus = body.decision;
      receiver.certifiedAt = new Date().toISOString();
      receiver.certifiedBy = actor.id;
      const upd = await dbUpdate("child_rearing.cra_receiver", receiver.id, receiver); if (upd === null) { const wl = await readJson(craReceiversFile, []); const wi = wl.findIndex((x) => x.id === receiver.id); if (wi !== -1) { wl[wi] = receiver; } await writeJson(craReceiversFile, wl); }
      audit(req, "cra-receiver.certify", { id: receiver.id, decision: body.decision });
      return reply(res, 200, receiver);
    }

    // ── 児童手当 対象児童 (CRA Children) ─────────────────────────────────

    const craChildrenMatch = pathname.match(/^\/api\/cra-receivers\/([^/]+)\/children$/);
    if (craChildrenMatch) {
      requireRole(actor, "CLERK");
      const receiverId = craChildrenMatch[1];
      const children = await readJson(craChildrenFile, []);
      if (req.method === "GET") return reply(res, 200, children.filter((c) => c.receiverId === receiverId));
      if (req.method === "POST") {
        const body = await parseBody(req);
        if (!body.fullName || !body.birthDate) return reply(res, 400, { error: "fullName, birthDate required" });
        const child = { id: crypto.randomUUID(), receiverId, fullName: body.fullName, birthDate: body.birthDate, createdBy: actor.id };
        children.push(child);
        await writeJson(craChildrenFile, children);
        audit(req, "cra-child.add", { id: child.id, receiverId });
        return reply(res, 201, child);
      }
    }

    // ── 児童扶養手当 手当額の自動算定（標準仕様書準拠）──
    const craAllowanceMatch = pathname.match(/^\/api\/cra-receivers\/([^/]+)\/allowance$/);
    if (craAllowanceMatch && req.method === "GET") {
      requireRole(actor, "CLERK");
      const receiverId = craAllowanceMatch[1];
      const receivers = (await dbSelect("child_rearing.cra_receiver")) ?? await readJson(craReceiversFile, []);
      const receiver = receivers.find((r) => r.id === receiverId);
      if (!receiver) return reply(res, 404, { error: "receiver not found" });
      const sp = new URL(req.url, "http://x").searchParams;
      const asOf = sp.get("asOf") ? new Date(sp.get("asOf")) : new Date();
      const allChildren = await readJson(craChildrenFile, []);
      // 支給対象児童 = 18歳到達後最初の3/31まで
      const eligible = allChildren.filter((c) => c.receiverId === receiverId && c.birthDate && asOf <= craFiscalYearEndAfterAge(c.birthDate, 18));
      const childCount = eligible.length;
      const dependents = sp.get("dependents") != null ? Number(sp.get("dependents")) : childCount;
      const result = computeChildRearingAllowance({ incomeYen: receiver.income ?? 0, dependents, childCount });
      return reply(res, 200, { receiverId, income: receiver.income ?? 0, dependents, ...result });
    }

    // ── 児童手当 支給 (CRA Payments) ─────────────────────────────────────

    if (pathname === "/api/cra-payments" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const payments = (await dbSelect("child_rearing.cra_payment")) ?? await readJson(craPaymentsFile, []);
      const { receiverId } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      return reply(res, 200, receiverId ? payments.filter((p) => p.receiverId === receiverId) : payments);
    }

    if (pathname === "/api/cra-payments" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const body = await parseBody(req);
      if (!body.receiverId || !body.period || body.amount === undefined) return reply(res, 400, { error: "receiverId, period, amount required" });
      const receiver = (await dbSelectOne("child_rearing.cra_receiver", body.receiverId))
        ?? (await readJson(craReceiversFile, [])).find((r) => r.id === body.receiverId);
      if (!receiver) return reply(res, 404, { error: "receiver not found" });
      if (receiver.certificationStatus !== "certified") return reply(res, 409, { error: "receiver is not certified" });
      const payments = (await dbSelect("child_rearing.cra_payment")) ?? await readJson(craPaymentsFile, []);
      const payment = { id: crypto.randomUUID(), receiverId: body.receiverId, period: body.period, amount: body.amount, status: "scheduled", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("child_rearing.cra_payment", payment); if (dbInserted === null) { payments.push(payment); await writeJson(craPaymentsFile, payments); }
      audit(req, "cra-payment.schedule", { id: payment.id, period: body.period });
      return reply(res, 201, payment);
    }

    const craPaymentPaidMatch = pathname.match(/^\/api\/cra-payments\/([^/]+)\/pay$/);
    if (craPaymentPaidMatch && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const payment = (await dbSelectOne("child_rearing.cra_payment", craPaymentPaidMatch[1]))
        ?? (await readJson(craPaymentsFile, [])).find((p) => p.id === craPaymentPaidMatch[1]);
      if (!payment) return reply(res, 404, { error: "payment not found" });
      if (payment.status === "paid") return reply(res, 409, { error: "already paid" });
      payment.status = "paid";
      payment.paidAt = new Date().toISOString();
      const upd = await dbUpdate("child_rearing.cra_payment", payment.id, payment); if (upd === null) { const wl = await readJson(craPaymentsFile, []); const wi = wl.findIndex((x) => x.id === payment.id); if (wi !== -1) { wl[wi] = payment; } await writeJson(craPaymentsFile, wl); }
      audit(req, "cra-payment.pay", { id: payment.id });
      return reply(res, 200, payment);
    }


    const craDeleteMatch = pathname.match(/^\/api\/cra-receivers\/([^\/]+)$/);
    if (craDeleteMatch && req.method === "DELETE") {
      requireRole(actor, "ADMIN");
      const delId = craDeleteMatch[1];
      const deleted = await dbDelete("child_rearing.cra_receiver", delId);
      if (deleted === null) {
        const _items = await readJson(craReceiversFile, []);
        const _idx = _items.findIndex((x) => x.id === delId);
        if (_idx === -1) return reply(res, 404, { error: "cra receiver not found" });
        _items.splice(_idx, 1);
        await writeJson(craReceiversFile, _items);
      }
      await audit(req, "cra-receiver.delete", { id: delId });
      return reply(res, 204, {});
    }


    // ── 児童扶養手当 手当額の試算（所得・扶養人数・児童数から直接算定。受給者登録不要）──
    if (pathname === "/api/cra/allowance/calc" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      const result = computeChildRearingAllowance({ incomeYen: body.income, dependents: body.dependents, childCount: body.childCount });
      return reply(res, 200, result);
    }

    // ════ 情報提供・しきい値判断・現況届バーコード・支払差止/解除・増減員・過払（標準仕様書）════

    const craGenkyoFile = join(dataRoot, "cra-genkyo.json");
    const craSashitomeFile = join(dataRoot, "cra-sashitome.json");
    const craKabaraiFile = join(dataRoot, "cra-kabarai.json");

    // ── 他システムへの受給者情報提供（就学援助・生活保護）──
    if (pathname === "/api/cra/provide" && req.method === "GET") {
      requireRole(actor, "ADMIN");
      const DATASETS = {
        "shugaku-enjo": { name: "受給者基本情報", target: "就学事務システム（就学援助）" },
        "seiho": { name: "受給者基本情報・支給情報", target: "生活保護システム" },
      };
      const dataset = searchParams.get("dataset") ?? "";
      const def = DATASETS[dataset];
      if (!def) return reply(res, 400, { error: `dataset は ${Object.keys(DATASETS).join("/")} のいずれか` });
      const receivers = await readJson(craReceiversFile, []);
      const rows = receivers.map((r) => ({ atenaId: r.atenaId ?? r.id, fullName: r.fullName ?? "",
        ...(dataset === "seiho" ? { supportKubun: r.supportKubun ?? "全部支給" } : {}) }));
      await audit(req, "cra.provide", { dataset, target: def.target, count: rows.length });
      return reply(res, 200, { dataset, name: def.name, target: def.target, count: rows.length, rows });
    }

    // ── 特定個人情報保護評価（PIA）のしきい値判断用 対象者情報の抽出 ──
    if (pathname === "/api/cra/ppia/threshold" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const b = await parseBody(req);
      const subjects = Array.isArray(b.subjects) ? b.subjects : [];
      if (!subjects.length) return reply(res, 400, { error: "subjects[]{atenaId,kubun(受給資格者/扶養義務者/対象児童)} required" });
      const byKubun = {};
      for (const s of subjects) byKubun[s.kubun ?? "不明"] = (byKubun[s.kubun ?? "不明"] ?? 0) + 1;
      await audit(req, "cra-ppia.threshold", { total: subjects.length });
      return reply(res, 200, { total: subjects.length, byKubun,
        thresholds: ["1,000人未満", "1,000〜10万人", "10万〜30万人", "30万人以上"],
        note: "特定個人情報保護評価のしきい値判断（対象人数×対象者区分）" });
    }

    // ── EUC（共通機能標準仕様書準拠の演算子・抽出・分析・加工・出力）：指定条件で一覧抽出→確認→加工 ──
    if (pathname === "/api/cra/euc/query" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const rows = Array.isArray(b.rows) ? b.rows : [];
      const conditions = Array.isArray(b.conditions) ? b.conditions : [];
      if (!rows.length) return reply(res, 400, { error: "rows[] required" });
      const OPS = {
        "=": (a, v) => String(a) === String(v), "≠": (a, v) => String(a) !== String(v),
        ">": (a, v) => Number(a) > Number(v), "<": (a, v) => Number(a) < Number(v),
        "≧": (a, v) => Number(a) >= Number(v), "≦": (a, v) => Number(a) <= Number(v),
        "部分一致": (a, v) => String(a).includes(String(v)),
        "前方一致": (a, v) => String(a).startsWith(String(v)),
        "後方一致": (a, v) => String(a).endsWith(String(v)),
      };
      const logic = b.logic === "or" ? "or" : "and";
      const test = (row) => conditions.map((c) => (OPS[c.op] ?? (() => false))(row[c.field], c.value));
      let hits = conditions.length ? rows.filter((r) => (logic === "and" ? test(r).every(Boolean) : test(r).some(Boolean))) : rows;
      if (Array.isArray(b.fields) && b.fields.length) hits = hits.map((r) => Object.fromEntries(b.fields.map((f) => [f, r[f]])));
      let aggregate = null;
      if (b.groupBy) {
        const groups = {};
        for (const r of hits) groups[String(r[b.groupBy])] = (groups[String(r[b.groupBy])] ?? 0) + 1;
        aggregate = Object.entries(groups).map(([key, count]) => ({ [b.groupBy]: key, count }));
      }
      await audit(req, "cra-euc.query", { hits: hits.length });
      return reply(res, 200, { count: hits.length, results: hits, aggregate });
    }

    // ── 現況届データの取込（システム規定の形式）──
    if (pathname === "/api/cra/genkyo/import" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const rows = Array.isArray(b.rows) ? b.rows : [];
      if (!rows.length) return reply(res, 400, { error: "rows[]{recipientId,fiscalYear,...} required" });
      const bad = rows.filter((r) => !r.recipientId || !r.fiscalYear);
      if (bad.length) return reply(res, 400, { error: "規定形式エラー（recipientId, fiscalYear必須）", badRows: bad.length });
      const genkyo = await readJson(craGenkyoFile, []);
      const items = rows.map((r) => ({ id: crypto.randomUUID(), recipientId: String(r.recipientId),
        fiscalYear: Number(r.fiscalYear), status: "取込済", importedAt: new Date().toISOString(), by: actor.id }));
      genkyo.unshift(...items);
      await writeJson(craGenkyoFile, genkyo);
      await audit(req, "cra-genkyo.import", { rows: items.length });
      return reply(res, 200, { imported: items.length, format: "システム規定形式（JSON/CSV変換取込）" });
    }

    // ── 現況届バーコードの生成・印字／読込受付（現況届・一部支給停止適用除外事由届）──
    if (pathname === "/api/cra/genkyo/barcode" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const kinds = { "現況届": "CRAG", "適用除外事由届": "CRAJ" };
      const kind = b.kind ?? "現況届";
      if (!b.recipientId || !kinds[kind]) return reply(res, 400, { error: `recipientId, kind(${Object.keys(kinds).join("/")}) required` });
      const fy = Number(b.fiscalYear ?? new Date().getFullYear());
      const barcode = `${kinds[kind]}-${fy}-${b.recipientId}`;
      await audit(req, "cra-genkyo.barcode", { kind, recipientId: b.recipientId });
      return reply(res, 200, { kind, recipientId: String(b.recipientId), fiscalYear: fy, barcode,
        note: "現況届に消込用バーコードとして印字" });
    }
    if (pathname === "/api/cra/genkyo/receive-barcode" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const m = String(b.barcode ?? "").match(/^(CRAG|CRAJ)-(\d{4})-(.+)$/);
      if (!m) return reply(res, 400, { error: "barcode形式エラー（CRAG|CRAJ-YYYY-recipientId）" });
      const kind = m[1] === "CRAG" ? "現況届" : "一部支給停止適用除外事由届";
      const genkyo = await readJson(craGenkyoFile, []);
      const item = { id: crypto.randomUUID(), kind, recipientId: m[3], fiscalYear: Number(m[2]),
        status: "受付済", receivedVia: "バーコード読込", receivedAt: new Date().toISOString(), by: actor.id };
      genkyo.unshift(item);
      await writeJson(craGenkyoFile, genkyo);
      await audit(req, "cra-genkyo.receive", { kind, recipientId: item.recipientId });
      return reply(res, 200, item);
    }

    // ── 現況届未提出者への支払差止（手動・自動）と差止解除 ──
    if (pathname === "/api/cra/genkyo/sashitome" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const modes = ["手動", "自動"];
      const recipients = Array.isArray(b.recipients) ? b.recipients : [];
      if (!modes.includes(b.mode) || !recipients.length) return reply(res, 400, { error: `mode(${modes.join("/")}), recipients[]{recipientId,submitted} required` });
      const targets = recipients.filter((r) => r.submitted !== true)
        .map((r) => ({ recipientId: String(r.recipientId), status: "支払差止", trigger: b.mode === "自動" ? "現況届未提出（自動）" : "現況届未提出（手動）" }));
      const sashitome = await readJson(craSashitomeFile, []);
      sashitome.unshift(...targets.map((t) => ({ ...t, at: new Date().toISOString(), by: actor.id })));
      await writeJson(craSashitomeFile, sashitome);
      await audit(req, "cra-sashitome.apply", { mode: b.mode, count: targets.length });
      return reply(res, 200, { mode: b.mode, stopped: targets.length, targets });
    }
    if (pathname === "/api/cra/sashitome/release" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.recipientId) return reply(res, 400, { error: "recipientId required" });
      const sashitome = await readJson(craSashitomeFile, []);
      const idx = sashitome.findIndex((s) => s.recipientId === String(b.recipientId) && s.status === "支払差止");
      if (idx === -1) return reply(res, 404, { error: "支払差止中の記録がありません" });
      sashitome[idx] = { ...sashitome[idx], status: "差止解除", releasedAt: new Date().toISOString(), releasedBy: actor.id };
      await writeJson(craSashitomeFile, sashitome);
      await audit(req, "cra-sashitome.release", { recipientId: b.recipientId });
      return reply(res, 200, sashitome[idx]);
    }

    // ── 支給停止通知書（一部支給・全部支給停止）──
    if (pathname === "/api/cra/notices/shikyu-teishi" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const kubuns = ["一部支給", "全部支給停止"];
      if (!b.recipientId || !kubuns.includes(b.kubun)) return reply(res, 400, { error: `recipientId, kubun(${kubuns.join("/")}) required` });
      await audit(req, "cra-notices.shikyu-teishi", { recipientId: b.recipientId, kubun: b.kubun });
      return reply(res, 200, { title: "児童扶養手当支給停止通知書", recipientId: String(b.recipientId),
        kubun: b.kubun, issuedAt: new Date().toISOString().slice(0, 10) });
    }

    // ── 額改定: 増員・減員する児童の選択 ──
    if (pathname === "/api/cra/children/adjust" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const actions = ["増員", "減員"];
      const childIds = Array.isArray(b.childIds) ? b.childIds : [];
      if (!b.recipientId || !actions.includes(b.action) || !childIds.length) {
        return reply(res, 400, { error: `recipientId, action(${actions.join("/")}), childIds[] required` });
      }
      await audit(req, "cra-children.adjust", { recipientId: b.recipientId, action: b.action, count: childIds.length });
      return reply(res, 200, { recipientId: String(b.recipientId), action: b.action,
        selectedChildren: childIds.map(String), effect: b.action === "増員" ? "翌月から増額改定" : "事由発生月の翌月から減額改定" });
    }

    // ── 過払処理（所得判定の結果過払発生時）＋過去資格の過払金との紐づけ ──
    if (pathname === "/api/cra/kabarai" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.recipientId || !Number(b.amount)) return reply(res, 400, { error: "recipientId, amount required" });
      const kabarai = await readJson(craKabaraiFile, []);
      const item = { id: crypto.randomUUID(), recipientId: String(b.recipientId), amount: Number(b.amount),
        cause: b.cause ?? "所得判定による支給区分変更", linkedQualificationId: b.linkedQualificationId ? String(b.linkedQualificationId) : null,
        status: "過払確定（返還対象）", at: new Date().toISOString(), by: actor.id };
      kabarai.unshift(item);
      await writeJson(craKabaraiFile, kabarai);
      await audit(req, "cra-kabarai.process", { recipientId: item.recipientId, linked: !!item.linkedQualificationId });
      return reply(res, 201, item);
    }
    if (pathname === "/api/cra/kabarai" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const recipientId = searchParams.get("recipientId") ?? "";
      const kabarai = await readJson(craKabaraiFile, []);
      const hits = recipientId ? kabarai.filter((k) => k.recipientId === recipientId) : kabarai;
      return reply(res, 200, { count: hits.length, items: hits });
    }

    // ── 扶養義務者の所得制限判定（標準仕様書 0200157/0200175/0200232）──
    //   民法877条1項の扶養義務者候補ごとに、扶養人数から所得限度額を算定し所得と比較。
    //   候補の中に1人でも所得超過がいれば支給停止と判定する。
    if (pathname === "/api/cra/fuyo-gimusha/judge" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = (await parseBody(req)) || {};
      const 基準額 = Number(b.基準額 ?? 2360000); // 扶養親族0人の所得限度額（令和基準）
      const 加算額 = Number(b.加算額 ?? 380000);   // 扶養親族1人につき加算
      const candidates = Array.isArray(b.候補) ? b.候補 : [];
      const results = candidates.map((c) => {
        const 扶養人数 = Math.max(0, Number(c.扶養人数) || 0);
        const 限度額 = 基準額 + 扶養人数 * 加算額;
        const 所得 = Number(c.所得) || 0;
        return { id: c.id ?? null, 扶養人数, 限度額, 所得, 所得超過: 所得 > 限度額 };
      });
      const 支給停止 = results.some((r) => r.所得超過);
      await audit(req, "cra.fuyo-gimusha-judge", { 支給停止, 候補数: results.length });
      return reply(res, 200, { 判定: 支給停止 ? "支給停止" : "支給", 支給停止, results,
        note: "扶養義務者候補ごとに扶養人数から限度額(基準額+扶養人数×加算額)を算定し所得と比較。1人でも超過なら支給停止。" });
    }

    // ── 児童扶養手当の認定/支払/額改定等の情報の管理（登録・修正・削除・照会）─────────────
    //   種別(月額算定/認定/未支払認定/支払結果/額改定)ごとに管理項目を柔軟保持しCRUDする。
    const craRecordsFile = join(dataRoot, "cra-records.json");
    if (pathname === "/api/cra/records" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 照会（種別/宛名番号フィルタ可）
      const 種別 = searchParams.get("種別"), atena = searchParams.get("宛名番号") ?? searchParams.get("受給者ID");
      const all = await readJson(craRecordsFile, []);
      const list = all.filter((x) => (!種別 || x.種別 === 種別) && (!atena || String(x.宛名番号 ?? x.受給者ID ?? "") === String(atena)));
      return reply(res, 200, { 件数: list.length, records: list });
    }
    if (pathname === "/api/cra/records" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 登録
      const b = await parseBody(req);
      if (!b.種別) return reply(res, 400, { error: "種別(月額算定/認定/未支払認定/支払結果/額改定) が必要です" });
      const all = await readJson(craRecordsFile, []);
      const { id: _i, createdAt: _c, ...fields } = b || {};
      const item = { id: crypto.randomUUID(), ...fields, createdAt: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(craRecordsFile, all);
      await audit(req, "cra-record.create", { id: item.id, 種別: item.種別 });
      return reply(res, 201, item);
    }
    const craRecordItemMatch = pathname.match(/^\/api\/cra\/records\/([^/]+)$/);
    if (craRecordItemMatch) {
      const all = await readJson(craRecordsFile, []);
      const idx = all.findIndex((x) => x.id === craRecordItemMatch[1]);
      if (req.method === "GET") { requireRole(actor, "CLERK"); return idx === -1 ? reply(res, 404, { error: "記録が見つかりません" }) : reply(res, 200, all[idx]); }
      if (req.method === "PUT") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "記録が見つかりません" });
        const b = await parseBody(req); const { id: _i, createdAt: _c, ...patch } = b || {};
        all[idx] = { ...all[idx], ...patch, updatedAt: new Date().toISOString() };
        await writeJson(craRecordsFile, all); await audit(req, "cra-record.update", { id: all[idx].id });
        return reply(res, 200, all[idx]);
      }
      if (req.method === "DELETE") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "記録が見つかりません" });
        const [removed] = all.splice(idx, 1);
        await writeJson(craRecordsFile, all); await audit(req, "cra-record.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

  return false;
}
