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

// ── 特定健診・特定保健指導の対象判定と受診率／実施率の算定（高齢者医療確保法）──
//   特定健診対象=40〜74歳の医療保険加入者。特定保健指導はメタボリスク階層化で動機付け支援/積極的支援を判定。
function computeTokuteiTarget({ age, insured = true }) {
  const a = Number(age);
  if (!Number.isFinite(a)) return { error: "age is required" };
  const eligible = insured && a >= 40 && a <= 74;
  return { age: a, eligible, criteria: "40〜74歳の医療保険加入者" };
}
// 特定保健指導の階層化（腹囲・BMI＋追加リスク: 血糖/脂質/血圧、喫煙）
function stratifyGuidance({ waist, bmi, highGlucose = false, highLipid = false, highBP = false, smoker = false, age }) {
  const obese = (Number(waist) >= 85 /* 男性基準・簡易 */) || (Number(bmi) >= 25);
  if (!obese) return { level: "情報提供", riskCount: 0 };
  let risk = (highGlucose ? 1 : 0) + (highLipid ? 1 : 0) + (highBP ? 1 : 0);
  const smokeAdd = smoker && risk >= 1 ? 1 : 0; // 喫煙は追加リスク1以上のとき1カウント
  const total = risk + smokeAdd;
  // 65歳以上は積極的支援でも動機付け支援
  let level;
  if (total >= 2) level = Number(age) >= 65 ? "動機付け支援" : "積極的支援";
  else if (total === 1) level = "動機付け支援";
  else level = "情報提供";
  return { level, riskCount: total };
}
function computeReceiptRate({ targets, received }) {
  const t = Number(targets) || 0, r = Number(received) || 0;
  return { targets: t, received: r, rate: t > 0 ? Math.round((r / t) * 1000) / 10 : 0 };
}
// ── 特定保健指導のポイント自動計算（0400164 計画登録時 / 0400175 結果登録時）──
//   各支援記録のポイントを合計し「継続的な支援によるポイント(合計)」と「ポイント(合計)」を算定。
//   初回面接以外の支援を継続的な支援とみなして加算。区分=計画なら[計画上の…]、
//   区分=結果なら[プロセス評価の…]とラベル付け（合計の算定ロジックは同一）。
//   ※どの支援形態が何ポイントか等は別紙の管理項目を参照（外部データ）。本算定は入力済みポイントの合算のみ。
function computeHokenShidoPoints({ 区分 = "計画", supports = [], 初回面接ポイント } = {}) {
  const kubun = String(区分) === "結果" ? "結果" : "計画";
  if (!Array.isArray(supports)) return { error: "supports[] required" };
  let total = 0, continuous = 0, initial = 0, aPoints = 0, bPoints = 0;
  const detail = [];
  for (const s of supports) {
    const pt = Number(s.ポイント ?? s.point ?? 0);
    if (!Number.isFinite(pt) || pt < 0) continue;
    const cat = String(s.種別 ?? s.category ?? "継続的支援");
    const isInitial = cat.includes("初回");
    total += pt;
    if (isInitial) { initial += pt; }
    else {
      continuous += pt;
      const shien = String(s.支援区分 ?? s.支援 ?? "");
      if (shien.includes("A")) aPoints += pt;
      if (shien.includes("B")) bPoints += pt;
    }
    detail.push({ 形態: s.形態 ?? s.mode ?? "", 種別: cat, 支援区分: s.支援区分 ?? s.支援 ?? null, ポイント: pt });
  }
  if (Number.isFinite(Number(初回面接ポイント))) { initial += Number(初回面接ポイント); total += Number(初回面接ポイント); }
  const plan = kubun === "計画";
  return {
    区分: kubun, 件数: supports.length,
    継続的な支援によるポイント合計: continuous,
    初回面接ポイント合計: initial,
    ポイント合計: total,
    支援A合計: aPoints, 支援B合計: bPoints,
    ラベル: {
      継続: plan ? "計画上の継続的な支援によるポイント(合計)" : "継続的なポイント(プロセス評価の合計)",
      合計: plan ? "計画上のポイント(合計)" : "ポイント(合計)",
    },
    内訳: detail,
  };
}

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 tokuteiSubjectsFile = join(dataRoot, "tokutei-subjects.json");
  const tokuteiResultsFile = join(dataRoot, "tokutei-results.json");


    // ── 特定健診 対象者 (Tokutei Subjects) ───────────────────────────────

    if (pathname === "/api/tokutei-subjects" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const subjects = (await dbSelect("tokutei_kenshin.tokutei_subject")) ?? await readJson(tokuteiSubjectsFile, []);
      const restrictions = await readJson(restrictionsFile, []);
      const { fiscalYear } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      const filtered = fiscalYear ? subjects.filter((s) => s.fiscalYear === fiscalYear) : subjects;
      return reply(res, 200, filtered.map((s) => maskRecord(actor, restrictions, s)));
    }

    if (pathname === "/api/tokutei-subjects" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.fullName || !body.birthDate || !body.insurerNumber || !body.fiscalYear) return reply(res, 400, { error: "fullName, birthDate, insurerNumber, fiscalYear required" });
      const subjects = (await dbSelect("tokutei_kenshin.tokutei_subject")) ?? await readJson(tokuteiSubjectsFile, []);
      const subject = { id: crypto.randomUUID(), subjectNumber: "TK-" + Date.now().toString(36).toUpperCase(), fullName: body.fullName, birthDate: body.birthDate, insurerNumber: body.insurerNumber, fiscalYear: body.fiscalYear, invitationSent: false, status: "target", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("tokutei_kenshin.tokutei_subject", subject); if (dbInserted === null) { subjects.push(subject); await writeJson(tokuteiSubjectsFile, subjects); }
      await audit(req, "tokutei-subject.register", { id: subject.id, fiscalYear: body.fiscalYear });
      return reply(res, 201, subject);
    }

    const subjectInviteMatch = pathname.match(/^\/api\/tokutei-subjects\/([^/]+)\/invite$/);
    if (subjectInviteMatch && req.method === "POST") {
      requireRole(actor, "CLERK");
      const subject = (await dbSelectOne("tokutei_kenshin.tokutei_subject", subjectInviteMatch[1]))
        ?? (await readJson(tokuteiSubjectsFile, [])).find((s) => s.id === subjectInviteMatch[1]);
      if (!subject) return reply(res, 404, { error: "subject not found" });
      if (subject.invitationSent) return reply(res, 409, { error: "invitation already sent" });
      subject.invitationSent = true;
      subject.invitationSentAt = new Date().toISOString();
      subject.status = "invited";
      const upd = await dbUpdate("tokutei_kenshin.tokutei_subject", subject.id, subject); if (upd === null) { const wl = await readJson(tokuteiSubjectsFile, []); const wi = wl.findIndex((x) => x.id === subject.id); if (wi !== -1) { wl[wi] = subject; } await writeJson(tokuteiSubjectsFile, wl); }
      await audit(req, "tokutei-subject.invite", { id: subject.id });
      return reply(res, 200, subject);
    }

    // ── 特定健診 結果 (Tokutei Results) ───────────────────────────────────

    if (pathname === "/api/tokutei-results" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const results = (await dbSelect("tokutei_kenshin.tokutei_result")) ?? await readJson(tokuteiResultsFile, []);
      const { subjectId } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      return reply(res, 200, subjectId ? results.filter((r) => r.subjectId === subjectId) : results);
    }

    if (pathname === "/api/tokutei-results" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.subjectId || !body.examinedDate) return reply(res, 400, { error: "subjectId, examinedDate required" });
      const subject = (await dbSelectOne("tokutei_kenshin.tokutei_subject", body.subjectId))
        ?? (await readJson(tokuteiSubjectsFile, [])).find((s) => s.id === body.subjectId);
      if (!subject) return reply(res, 404, { error: "subject not found" });
      const results = (await dbSelect("tokutei_kenshin.tokutei_result")) ?? await readJson(tokuteiResultsFile, []);
      if (results.some((item) => item.subjectId === body.subjectId)) return reply(res, 409, { error: "result already recorded" });
      const result = { id: crypto.randomUUID(), subjectId: body.subjectId, examinedDate: body.examinedDate, bmi: body.bmi ?? null, waist: body.waist ?? null, bloodPressureSystolic: body.bloodPressureSystolic ?? null, bloodPressureDiastolic: body.bloodPressureDiastolic ?? null, metabolicSyndromeJudgment: body.metabolicSyndromeJudgment ?? "normal", supportLevel: body.supportLevel ?? "none", recordedBy: actor.id, recordedAt: new Date().toISOString() };
      results.push(result);
      subject.status = "completed";
      const tri = await dbInsert("tokutei_kenshin.tokutei_result", result); if (tri === null) { await writeJson(tokuteiResultsFile, results); }
      const upd = await dbUpdate("tokutei_kenshin.tokutei_subject", subject.id, subject); if (upd === null) { const wl = await readJson(tokuteiSubjectsFile, []); const wi = wl.findIndex((x) => x.id === subject.id); if (wi !== -1) { wl[wi] = subject; } await writeJson(tokuteiSubjectsFile, wl); }
      await audit(req, "tokutei-result.record", { id: result.id, subjectId: body.subjectId });
      return reply(res, 201, result);
    }


    const subjectMatch = pathname.match(/^\/api\/tokutei-subjects\/([^/]+)$/);
    if (subjectMatch && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, "CLERK");
      const id = subjectMatch[1];
      const subject = (await dbSelectOne("tokutei_kenshin.tokutei_subject", id))
        ?? (await readJson(tokuteiSubjectsFile, [])).find((s) => s.id === id);
      if (!subject) return reply(res, 404, { error: "tokutei subject not found" });
      if (req.method === "GET") return reply(res, 200, subject);

      const body = await parseBody(req);
      const editable = ["fullName", "birthDate", "insurerNumber", "fiscalYear"];
      const patch = {};
      for (const k of editable) if (body[k] !== undefined) patch[k] = body[k];
      patch.updatedAt = new Date().toISOString();
      patch.updatedBy = actor.id;
      const updated = await dbUpdate("tokutei_kenshin.tokutei_subject", id, patch);
      let result = updated;
      if (updated === null) {
        const items = await readJson(tokuteiSubjectsFile, []);
        const idx = items.findIndex((x) => x.id === id);
        items[idx] = { ...items[idx], ...patch };
        await writeJson(tokuteiSubjectsFile, items);
        result = items[idx];
      }
      await audit(req, "tokutei-subject.update", { id, fields: Object.keys(patch).filter((k) => k !== "updatedAt" && k !== "updatedBy") });
      return reply(res, 200, result);
    }

    const subjectDeleteMatch = pathname.match(/^\/api\/tokutei-subjects\/([^\/]+)$/);
    if (subjectDeleteMatch && req.method === "DELETE") {
      requireRole(actor, "ADMIN");
      const delId = subjectDeleteMatch[1];
      const deleted = await dbDelete("tokutei_kenshin.tokutei_subject", delId);
      if (deleted === null) {
        const _items = await readJson(tokuteiSubjectsFile, []);
        const _idx = _items.findIndex((x) => x.id === delId);
        if (_idx === -1) return reply(res, 404, { error: "tokutei subject not found" });
        _items.splice(_idx, 1);
        await writeJson(tokuteiSubjectsFile, _items);
      }
      await audit(req, "tokutei-subject.delete", { id: delId });
      return reply(res, 204, {});
    }


    // ── 特定健診の対象判定・保健指導の階層化・受診率算定（標準仕様書準拠）──
    if (pathname === "/api/tokutei/target" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      const result = computeTokuteiTarget(body || {});
      if (result.error) return reply(res, 400, result);
      return reply(res, 200, result);
    }
    if (pathname === "/api/tokutei/guidance" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = stratifyGuidance((await parseBody(req)) || {});
      return reply(res, 200, result);
    }
    if (pathname === "/api/tokutei/receipt-rate" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeReceiptRate((await parseBody(req)) || {});
      return reply(res, 200, result);
    }

    // ════ 帳票出力（0400090準拠）・経年EUC・情報提供・世帯主出力・動機付け支援相当（標準仕様書）════

    // ── 健診帳票の共通出力（機能ID0400090に準じた出力。再発行印字・過去受診歴・電子カルテ共有由来に対応）──
    const TOKUTEI_DOC_TYPES = [
      "特定健診受診券", "特定健診受診券（セット券）", "特定健診質問票", "健診案内通知",
      "特定健診結果通知表", "特定保健指導利用券", "特定保健指導利用券（電子カルテ情報共有サービス）",
      "後期高齢者健診受診券", "後期高齢者健診質問票", "後期高齢者健診結果通知表",
    ];
    if (pathname === "/api/tokutei/documents" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId || !TOKUTEI_DOC_TYPES.includes(b.docType)) {
        return reply(res, 400, { error: "atenaId, docType required", docTypes: TOKUTEI_DOC_TYPES });
      }
      const doc = {
        docType: b.docType, atenaId: String(b.atenaId), basedOn: "機能ID0400090に準じた共通出力",
        contract: b.docType.includes("受診券") ? "集合契約以外" : undefined,
        reissueMark: b.reissue === true && (b.docType.includes("受診券") || b.docType.includes("利用券")) ? "【再発行】" : undefined,
        issuedAt: new Date().toISOString().slice(0, 10),
      };
      if (b.docType.includes("結果通知表")) {
        const results = (await readJson(tokuteiResultsFile, [])).filter((r) => String(r.atenaId ?? r.subjectId ?? "") === String(b.atenaId));
        doc.pastResults = b.includeHistory === false ? [] : results; // 過去受診歴を含めて出力
      }
      await audit(req, "tokutei-documents.print", { docType: b.docType, atenaId: b.atenaId, reissue: !!b.reissue });
      return reply(res, 200, doc);
    }
    if (pathname === "/api/tokutei/documents" && req.method === "GET") {
      requireRole(actor, "CLERK");
      return reply(res, 200, { docTypes: TOKUTEI_DOC_TYPES });
    }

    // ── 住民ごとの健診結果 経年確認（EUC。電子カルテ情報共有サービス由来も統合）──
    if (pathname === "/api/tokutei/results/timeline" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId) return reply(res, 400, { error: "atenaId required" });
      const stored = (await readJson(tokuteiResultsFile, [])).filter((r) => String(r.atenaId ?? r.subjectId ?? "") === String(b.atenaId));
      const ehr = (Array.isArray(b.ehrResults) ? b.ehrResults : []).map((r) => ({ ...r, source: "電子カルテ情報共有サービス" }));
      const merged = [...stored.map((r) => ({ ...r, source: r.source ?? "自システム" })), ...ehr]
        .sort((a, x) => String(a.fiscalYear ?? "").localeCompare(String(x.fiscalYear ?? "")));
      await audit(req, "tokutei-results.timeline", { atenaId: b.atenaId, years: merged.length });
      return reply(res, 200, { atenaId: String(b.atenaId), kind: "経年確認（EUC）", count: merged.length, timeline: merged });
    }

    // ── 独自施策システムへの情報提供（基本データリストの範囲）──
    if (pathname === "/api/tokutei/provide" && req.method === "GET") {
      requireRole(actor, "ADMIN");
      const subjects = await readJson(tokuteiSubjectsFile, []);
      const rows = subjects.map((s) => ({ atenaId: s.atenaId ?? s.id, fullName: s.fullName ?? "",
        fiscalYear: s.fiscalYear ?? "", status: s.status ?? "" }));
      await audit(req, "tokutei.provide", { target: "独自施策システム", count: rows.length });
      return reply(res, 200, { target: "独自施策システム", scope: "基本データリストに定める範囲", count: rows.length, rows });
    }

    // ── 同一世帯に複数対象者がいる場合の世帯主1人分出力 ──
    if (pathname === "/api/tokutei/output/per-household" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const targets = Array.isArray(b.targets) ? b.targets : [];
      if (!targets.length) return reply(res, 400, { error: "targets[]{atenaId,householdId,isHead} required" });
      const byHousehold = {};
      for (const t of targets) (byHousehold[t.householdId ?? "不明"] ??= []).push(t);
      const recipients = Object.values(byHousehold).map((members) =>
        (members.find((m) => m.isHead === true) ?? members[0]).atenaId);
      await audit(req, "tokutei-output.per-household", { households: recipients.length });
      return reply(res, 200, { households: Object.keys(byHousehold).length, recipients });
    }

    // ── 動機付け支援相当となる対象者の抽出 ──
    if (pathname === "/api/tokutei/shidou/extract" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const subjects = Array.isArray(b.subjects) ? b.subjects : [];
      if (!subjects.length) return reply(res, 400, { error: "subjects[]{atenaId,waist,bmi,highGlucose,highLipid,highBP,smoker,age} required" });
      const extracted = subjects.map((s) => ({ atenaId: s.atenaId, ...stratifyGuidance(s) }))
        .filter((r) => r.level === "動機付け支援");
      await audit(req, "tokutei-shidou.extract", { checked: subjects.length, extracted: extracted.length });
      return reply(res, 200, { kind: "動機付け支援相当の対象者抽出", checked: subjects.length,
        count: extracted.length, targets: extracted });
    }

    // ── 特定保健指導ポイント自動計算（0400164 計画登録時 / 0400175 結果登録時）────
    if (pathname === "/api/tokutei/hoken-shido/points" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const supports = Array.isArray(b.supports) ? b.supports : Array.isArray(b.支援記録) ? b.支援記録 : null;
      if (!supports) return reply(res, 400, { error: "supports[]{種別,形態,支援区分,ポイント} required, 区分:計画|結果" });
      const result = computeHokenShidoPoints({ 区分: b.区分 ?? b.kubun, supports, 初回面接ポイント: b.初回面接ポイント });
      await audit(req, "tokutei-hoken-shido.points", { 区分: result.区分, 件数: result.件数, ポイント合計: result.ポイント合計 });
      return reply(res, 200, { kind: "特定保健指導ポイント自動計算", ...result });
    }

    // ── 特定健診等の各種情報の管理（登録・修正・削除・照会）: 事業従事者/事業予定/住登外/除外対象者/予約希望者/保健指導区分/特定保健指導計画・結果・後服薬/後期高齢者健診結果等を種別別CRUD保持 ──
    const tokuteiRecordsFile = join(dataRoot, "tokutei-records.json");
    if (pathname === "/api/tokutei/records" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const 種別 = searchParams.get("種別"), atena = searchParams.get("宛名番号") ?? searchParams.get("atenaId");
      const all = await readJson(tokuteiRecordsFile, []);
      const list = all.filter((x) => (!種別 || x.種別 === 種別) && (!atena || String(x.宛名番号 ?? x.atenaId ?? "") === String(atena)));
      return reply(res, 200, { 件数: list.length, records: list });
    }
    if (pathname === "/api/tokutei/records" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.種別) return reply(res, 400, { error: "種別(事業従事者/事業予定/住登外/除外対象者/予約希望者/保健指導区分/特定保健指導計画/結果/後服薬/後期高齢者健診結果 等) が必要です" });
      const all = await readJson(tokuteiRecordsFile, []);
      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(tokuteiRecordsFile, all);
      await audit(req, "tokutei-record.create", { id: item.id, 種別: item.種別 });
      return reply(res, 201, item);
    }
    const tokuteiRecordItemMatch = pathname.match(/^\/api\/tokutei\/records\/([^/]+)$/);
    if (tokuteiRecordItemMatch) {
      const all = await readJson(tokuteiRecordsFile, []);
      const idx = all.findIndex((x) => x.id === tokuteiRecordItemMatch[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(tokuteiRecordsFile, all); await audit(req, "tokutei-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(tokuteiRecordsFile, all); await audit(req, "tokutei-record.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

  return false;
}
