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

// ── 就学 学齢・学年の判定（学校教育法: 満6歳に達した日の翌日以後の最初の学年から小学校就学）──
function ageAt(birthDate, at) {
  const b = new Date(birthDate), a = new Date(at);
  let age = a.getFullYear() - b.getFullYear();
  if (a.getMonth() < b.getMonth() || (a.getMonth() === b.getMonth() && a.getDate() < b.getDate())) age -= 1;
  return age;
}
function computeSchoolGrade({ birthDate, asOf }) {
  if (!birthDate) return { error: "birthDate is required" };
  const at = asOf ? new Date(asOf) : new Date();
  // 当該年度（4月始まり）の4月1日時点の年齢で学年が決まる
  const schoolYear = at.getMonth() >= 3 ? at.getFullYear() : at.getFullYear() - 1;
  const april1 = new Date(schoolYear, 3, 1);
  const ageAtApril1 = ageAt(birthDate, april1);
  // 6歳→小1, …, 11歳→小6, 12歳→中1, …, 14歳→中3
  const gradeNum = ageAtApril1 - 5;
  let stage, grade;
  if (gradeNum < 1) { stage = "就学前"; grade = null; }
  else if (gradeNum <= 6) { stage = "小学校"; grade = gradeNum; }
  else if (gradeNum <= 9) { stage = "中学校"; grade = gradeNum - 6; }
  else { stage = "中学校卒業後"; grade = null; }
  // 小学校就学始期（満6歳に達した日の翌日以後の最初の4/1）の年度。
  // 1/1〜4/1生まれ(早生まれ)は birthYear+6、4/2〜12/31生まれは birthYear+7。
  const b = new Date(birthDate);
  const earlyBirth = b.getMonth() < 3 || (b.getMonth() === 3 && b.getDate() === 1);
  const enrollmentYear = b.getFullYear() + (earlyBirth ? 6 : 7);
  return { schoolYear, ageAtApril1, stage, grade, gradeLabel: grade ? `${stage}${grade}年` : stage, primaryEnrollmentYear: enrollmentYear };
}

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 schoolChildrenFile = join(dataRoot, "school-children.json");
  const enrollmentsFile = join(dataRoot, "enrollments.json");
  const shugakuDocsFile = join(dataRoot, "shugaku-documents.json");

  // 就学帳票の種別と出力順パターン（出力項目をパターン化し、選択して出力できる）
  const SHUGAKU_DOC_TYPES = ["学齢簿", "入学通知書", "就学時健康診断通知書", "指定校変更許可書", "区域外就学協議書", "区域外就学承諾書"];
  const SORT_PATTERNS = ["学校順", "学年・組順", "五十音順", "住所順", "生年月日順"];


    // ── 就学 対象児童 (School Children) ──────────────────────────────────

    if (pathname === "/api/school-children" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const children = (await dbSelect("school_enrollment.school_child")) ?? await readJson(schoolChildrenFile, []);
      const restrictions = await readJson(restrictionsFile, []);
      const { schoolYear } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      const filtered = schoolYear ? children.filter((c) => c.schoolYear === schoolYear) : children;
      return reply(res, 200, filtered.map((c) => maskRecord(actor, restrictions, c)));
    }

    if (pathname === "/api/school-children" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.fullName || !body.birthDate || !body.address) return reply(res, 400, { error: "fullName, birthDate, address required" });
      const children = (await dbSelect("school_enrollment.school_child")) ?? await readJson(schoolChildrenFile, []);
      const schoolYear = String(new Date(body.birthDate).getFullYear() + 6);
      const child = { id: crypto.randomUUID(), childNumber: "SC-" + Date.now().toString(36).toUpperCase(), fullName: body.fullName, birthDate: body.birthDate, address: body.address, guardianName: body.guardianName ?? "", schoolYear, status: "pre_enrollment", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("school_enrollment.school_child", child); if (dbInserted === null) { children.push(child); await writeJson(schoolChildrenFile, children); }
      await audit(req, "school-child.register", { id: child.id, schoolYear });
      return reply(res, 201, child);
    }

    const schoolChildMatch = pathname.match(/^\/api\/school-children\/([^/]+)$/);
    if (schoolChildMatch) {
      requireRole(actor, "CLERK");
      const child = (await dbSelectOne("school_enrollment.school_child", schoolChildMatch[1]))
        ?? (await readJson(schoolChildrenFile, [])).find((c) => c.id === schoolChildMatch[1]);
      if (!child) return reply(res, 404, { error: "child not found" });
      if (req.method === "GET") return reply(res, 200, child);
      if (req.method === "PUT") {
        const body = await parseBody(req);
        Object.assign(child, body, { id: child.id, updatedAt: new Date().toISOString() });
        const upd = await dbUpdate("school_enrollment.school_child", child.id, child); if (upd === null) { const wl = await readJson(schoolChildrenFile, []); const wi = wl.findIndex((x) => x.id === child.id); if (wi !== -1) { wl[wi] = child; } await writeJson(schoolChildrenFile, wl); }
        await audit(req, "school-child.update", { id: child.id });
        return reply(res, 200, child);
      }
    }

    // ── 就学通知 (Enrollments) ────────────────────────────────────────────

    if (pathname === "/api/enrollments" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const enrollments = (await dbSelect("school_enrollment.enrollment")) ?? await readJson(enrollmentsFile, []);
      const { childId, schoolYear } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      let result = enrollments;
      if (childId) result = result.filter((e) => e.childId === childId);
      if (schoolYear) result = result.filter((e) => e.schoolYear === schoolYear);
      return reply(res, 200, result);
    }

    if (pathname === "/api/enrollments" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.childId || !body.assignedSchool || !body.schoolYear) return reply(res, 400, { error: "childId, assignedSchool, schoolYear required" });
      const child = (await dbSelectOne("school_enrollment.school_child", body.childId))
        ?? (await readJson(schoolChildrenFile, [])).find((c) => c.id === body.childId);
      if (!child) return reply(res, 404, { error: "child not found" });
      const enrollments = (await dbSelect("school_enrollment.enrollment")) ?? await readJson(enrollmentsFile, []);
      const existing = enrollments.find((e) => e.childId === body.childId && e.schoolYear === body.schoolYear);
      if (existing) return reply(res, 409, { error: "enrollment already exists for this child and school year" });
      const enrollment = { id: crypto.randomUUID(), childId: body.childId, assignedSchool: body.assignedSchool, schoolYear: body.schoolYear, notificationSentAt: null, status: "assigned", createdBy: actor.id, createdAt: new Date().toISOString() };
      enrollments.push(enrollment);
      child.status = "enrolled";
      await writeJson(enrollmentsFile, enrollments);
      const upd = await dbUpdate("school_enrollment.school_child", child.id, child); if (upd === null) { const wl = await readJson(schoolChildrenFile, []); const wi = wl.findIndex((x) => x.id === child.id); if (wi !== -1) { wl[wi] = child; } await writeJson(schoolChildrenFile, wl); }
      await audit(req, "enrollment.assign", { id: enrollment.id, childId: body.childId, assignedSchool: body.assignedSchool });
      return reply(res, 201, enrollment);
    }

    const enrollmentMatch = pathname.match(/^\/api\/enrollments\/([^/]+)$/);
    if (enrollmentMatch && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, "CLERK");
      const id = enrollmentMatch[1];
      const item = (await dbSelectOne("school_enrollment.enrollment", id))
        ?? (await readJson(enrollmentsFile, [])).find((e) => e.id === id);
      if (!item) return reply(res, 404, { error: "enrollment not found" });
      if (req.method === "GET") return reply(res, 200, item);

      if (item.status !== "assigned") return reply(res, 409, { error: "can only edit enrollments in assigned status (before notification)" });
      const body = await parseBody(req);
      const editable = ["assignedSchool", "schoolYear"];
      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("school_enrollment.enrollment", id, patch);
      let result = updated;
      if (updated === null) {
        const items = await readJson(enrollmentsFile, []);
        const idx = items.findIndex((x) => x.id === id);
        items[idx] = { ...items[idx], ...patch };
        await writeJson(enrollmentsFile, items);
        result = items[idx];
      }
      await audit(req, "enrollment.update", { id, fields: Object.keys(patch).filter((k) => k !== "updatedAt" && k !== "updatedBy") });
      return reply(res, 200, result);
    }

    const enrollmentNotifyMatch = pathname.match(/^\/api\/enrollments\/([^/]+)\/notify$/);
    if (enrollmentNotifyMatch && req.method === "POST") {
      requireRole(actor, "CLERK");
      const enrollment = (await dbSelectOne("school_enrollment.enrollment", enrollmentNotifyMatch[1]))
        ?? (await readJson(enrollmentsFile, [])).find((e) => e.id === enrollmentNotifyMatch[1]);
      if (!enrollment) return reply(res, 404, { error: "enrollment not found" });
      if (enrollment.notificationSentAt) return reply(res, 409, { error: "notification already sent" });
      enrollment.notificationSentAt = new Date().toISOString();
      enrollment.status = "notified";
      const upd = await dbUpdate("school_enrollment.enrollment", enrollment.id, enrollment); if (upd === null) { const wl = await readJson(enrollmentsFile, []); const wi = wl.findIndex((x) => x.id === enrollment.id); if (wi !== -1) { wl[wi] = enrollment; } await writeJson(enrollmentsFile, wl); }
      await audit(req, "enrollment.notify", { id: enrollment.id });
      return reply(res, 200, enrollment);
    }


    const childDeleteMatch = pathname.match(/^\/api\/school-children\/([^\/]+)$/);
    if (childDeleteMatch && req.method === "DELETE") {
      requireRole(actor, "ADMIN");
      const delId = childDeleteMatch[1];
      const deleted = await dbDelete("school_enrollment.school_child", delId);
      if (deleted === null) {
        const _items = await readJson(schoolChildrenFile, []);
        const _idx = _items.findIndex((x) => x.id === delId);
        if (_idx === -1) return reply(res, 404, { error: "school child not found" });
        _items.splice(_idx, 1);
        await writeJson(schoolChildrenFile, _items);
      }
      await audit(req, "school-child.delete", { id: delId });
      return reply(res, 204, {});
    }


    // ── 就学 学齢・学年の判定（学校教育法準拠）──
    if (pathname === "/api/school/grade" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      const result = computeSchoolGrade(body || {});
      if (result.error) return reply(res, 400, result);
      await audit(req, "school.grade-calc", { stage: result.stage, grade: result.grade });
      return reply(res, 200, result);
    }

    // ════ 就学帳票出力・区域外就学・教育人口推計（標準仕様書）════

    // ── 就学帳票の出力（出力順パターン選択・保護者用/学校用・個別発行・再発行）──
    if (pathname === "/api/shugaku/documents/print" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!SHUGAKU_DOC_TYPES.includes(b.docType)) {
        return reply(res, 400, { error: `docType は ${SHUGAKU_DOC_TYPES.join("/")} のいずれか` });
      }
      const sortPattern = SORT_PATTERNS.includes(b.sortPattern) ? b.sortPattern : "五十音順";
      const variant = b.variant === "学校用" ? "学校用" : "保護者用"; // 学齢簿は2種類の出力
      const docs = await readJson(shugakuDocsFile, []);
      const doc = {
        id: crypto.randomUUID(), docType: String(b.docType), sortPattern, variant,
        targetIds: Array.isArray(b.targetIds) ? b.targetIds.map(String) : [], // 個別発行（対象者指定）
        individual: Array.isArray(b.targetIds) && b.targetIds.length > 0,
        reissue: b.reissue === true, // 紛失等による保護者申出の再発行
        printedBy: actor.id, printedAt: new Date().toISOString(),
      };
      docs.unshift(doc);
      await writeJson(shugakuDocsFile, docs);
      await audit(req, "shugaku-doc.print", { docType: doc.docType, sortPattern, individual: doc.individual, reissue: doc.reissue });
      return reply(res, 201, doc);
    }
    if (pathname === "/api/shugaku/documents" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const docs = await readJson(shugakuDocsFile, []);
      return reply(res, 200, docs.slice(0, 300));
    }
    if (pathname === "/api/shugaku/documents/patterns" && req.method === "GET") {
      requireRole(actor, "CLERK");
      return reply(res, 200, { docTypes: SHUGAKU_DOC_TYPES, sortPatterns: SORT_PATTERNS });
    }

    // ── 区域外就学（市町村外の児童生徒の就学申請→相手先市町村宛の協議書＋承諾書のセット出力）──
    if (pathname === "/api/shugaku/kuikigai/apply" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.childAtenaId || !b.counterpartMunicipality || !b.school) {
        return reply(res, 400, { error: "childAtenaId, counterpartMunicipality(相手先市町村), school required" });
      }
      const docs = await readJson(shugakuDocsFile, []);
      const issued = [
        { id: crypto.randomUUID(), docType: "区域外就学協議書", addressedTo: String(b.counterpartMunicipality), childAtenaId: String(b.childAtenaId), school: String(b.school), printedBy: actor.id, printedAt: new Date().toISOString() },
        { id: crypto.randomUUID(), docType: "区域外就学承諾書", addressedTo: String(b.counterpartMunicipality), childAtenaId: String(b.childAtenaId), school: String(b.school), printedBy: actor.id, printedAt: new Date().toISOString() },
      ];
      docs.unshift(...issued);
      await writeJson(shugakuDocsFile, docs);
      await audit(req, "shugaku-kuikigai.apply", { counterpart: b.counterpartMunicipality, school: b.school });
      return reply(res, 201, { documents: issued.map((d) => d.docType), addressedTo: b.counterpartMunicipality });
    }

    // ── 教育人口等推計表（学区・地域別。生年月日→入学年度の学年別人数を推計）──
    if (pathname === "/api/shugaku/population/estimate" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.children)) return reply(res, 400, { error: "children[]{birthDate,schoolZone} required" });
      const byZone = {};
      for (const c of b.children) {
        const zone = c.schoolZone ?? c.district ?? "未指定";
        const birth = new Date(c.birthDate);
        if (Number.isNaN(birth.getTime())) continue;
        // 満6歳に達した日の翌日以後の最初の4月1日が入学年度（4/1生まれ以前は同年度）
        const entryYear = birth.getMonth() + 1 >= 4 && !(birth.getMonth() + 1 === 4 && birth.getDate() === 1)
          ? birth.getFullYear() + 7 : birth.getFullYear() + 6;
        (byZone[zone] ??= {});
        byZone[zone][entryYear] = (byZone[zone][entryYear] ?? 0) + 1;
      }
      const table = Object.entries(byZone).map(([zone, years]) => ({ zone, byEntryYear: years }));
      await audit(req, "shugaku-population.estimate", { zones: table.length, children: b.children.length });
      return reply(res, 200, { document: "教育人口等推計表（学区・地域別）", table });
    }

    // ════ 学校選択制調査書・外国籍案内・公印・住基連携・行政区メッセージ・マニュアル（標準仕様書）════

    const shugakuSealFile = join(dataRoot, "shugaku-seal.json");
    const shugakuMessagesFile = join(dataRoot, "shugaku-messages.json");

    // ── 学校選択制の案内＋学校選択制調査書の併せ出力 ──
    if (pathname === "/api/shugaku/school-choice/survey" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.childId) return reply(res, 400, { error: "childId required" });
      await audit(req, "shugaku-school-choice.survey", { childId: b.childId });
      return reply(res, 200, { childId: String(b.childId),
        documents: ["学校選択制の案内", "学校選択制調査書"],
        note: "案内に併せて学校選択制調査書も出力" });
    }

    // ── 不就学の外国籍児童生徒への就学案内 ──
    if (pathname === "/api/shugaku/foreign/guidance" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const children = Array.isArray(b.children) ? b.children : [];
      if (!children.length) return reply(res, 400, { error: "children[]{childId,name,nationality,enrolled} required" });
      const targets = children.filter((c) => c.enrolled !== true);
      const notices = targets.map((c) => ({ childId: c.childId, name: c.name ?? "",
        document: "外国籍児童への就学案内", languages: ["日本語", "英語", "中国語", "ポルトガル語"] }));
      await audit(req, "shugaku-foreign.guidance", { targets: notices.length });
      return reply(res, 200, { kind: "不就学の外国籍児童生徒向け就学案内", count: notices.length, notices });
    }

    // ── 電子公印の設定（教育長/職務代理者の種類選択・都道府県名印字・固定文言・最大25mm角黒色）──
    if (pathname === "/api/shugaku/seal" && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, req.method === "PUT" ? "ADMIN" : "CLERK");
      const defaults = { sealKind: "教育長の印", prefName: "○○県", cityName: "○○市", isDeputy: false,
        fixedNote: "この印は黒色です", sizeMm: 25, color: "黒" };
      let conf = { ...defaults, ...(await readJson(shugakuSealFile, {})) };
      if (req.method === "PUT") {
        const b = await parseBody(req);
        const kinds = ["教育長の印", "職務代理者の印"];
        if (b.sealKind !== undefined && !kinds.includes(b.sealKind)) return reply(res, 400, { error: `sealKind(${kinds.join("/")})` });
        if (b.sizeMm !== undefined && Number(b.sizeMm) > 25) return reply(res, 400, { error: "電子公印は最大25mm角（黒色）" });
        conf = { ...conf, ...b, updatedAt: new Date().toISOString(), updatedBy: actor.id };
        await writeJson(shugakuSealFile, conf);
        await audit(req, "shugaku-seal.update", { sealKind: conf.sealKind });
      }
      const title = conf.isDeputy ? "教育長職務代理者" : "教育委員会教育長";
      // 指定都市・特別区の場合も含め都道府県名を印字
      const issuerLine = `${conf.prefName}${conf.cityName}${title}`;
      return reply(res, 200, { ...conf, issuerLine,
        spec: { maxSizeMm: 25, color: "黒", electronic: true }, fixedNoteApplied: conf.fixedNote });
    }

    // ── 住所の住民記録システム連携取得 ──
    if (pathname === "/api/shugaku/address/from-juki" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId || !b.jukiAddress) return reply(res, 400, { error: "atenaId, jukiAddress required" });
      await audit(req, "shugaku-address.from-juki", { atenaId: b.atenaId });
      return reply(res, 200, { atenaId: String(b.atenaId), address: String(b.jukiAddress),
        source: "住民記録システム", note: "住所は住民記録システムからの連携値を正とする" });
    }

    // ── 教育委員会→行政区（区役所）へのメッセージ送付 ──
    if (pathname === "/api/shugaku/messages" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.toWard || !b.message) return reply(res, 400, { error: "toWard, message required" });
      const messages = await readJson(shugakuMessagesFile, []);
      const item = { id: crypto.randomUUID(), from: "教育委員会", toWard: String(b.toWard),
        message: String(b.message), sentAt: new Date().toISOString(), by: actor.id };
      messages.unshift(item);
      await writeJson(shugakuMessagesFile, messages);
      await audit(req, "shugaku-messages.send", { toWard: item.toWard });
      return reply(res, 201, item);
    }
    if (pathname === "/api/shugaku/messages" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const toWard = searchParams.get("toWard") ?? "";
      const messages = await readJson(shugakuMessagesFile, []);
      const hits = toWard ? messages.filter((m) => m.toWard === toWard) : messages;
      return reply(res, 200, { count: hits.length, messages: hits.slice(0, 100) });
    }

    // ── マニュアル（オンライン＝画面別ヘルプ／冊子）──
    if (pathname === "/api/shugaku/manual" && req.method === "GET") {
      const MANUAL = {
        domain: { title: "就学事務管理", body: "学齢簿・就学校指定・区域外就学の操作手順。" },
        reports: { title: "帳票", body: "通知書・調査書・案内の出力手順。" },
        admin: { title: "管理", body: "公印設定・行政区メッセージ・連携の運用手順。" },
      };
      const screen = searchParams.get("screen") ?? "";
      if (screen) {
        const page = MANUAL[screen];
        if (!page) return reply(res, 404, { error: "unknown screen" });
        return reply(res, 200, { screen, ...page, kind: "オンラインマニュアル（画面別ヘルプ）" });
      }
      const format = searchParams.get("format") ?? "online";
      return reply(res, 200, { kind: format === "booklet" ? "冊子マニュアル（印刷用・全章）" : "オンラインマニュアル（目次）",
        chapters: Object.entries(MANUAL).map(([k, v]) => ({ screen: k, title: v.title })) });
    }

  return false;
}
