/**
 * Zod schemas for case mutations (create / update).
 * Shapes mirror the FastAPI Pydantic models in web/app.py:
 *   - CaseCreateRequest
 *   - CaseUpdateRequest
 *
 * Validation rules are stricter on the UI than on the backend so the user
 * gets Hebrew-localized error messages at the field level instead of a
 * 422 blob from FastAPI.
 */

import { z } from "zod";
import type { PracticeArea, AppealSubtype } from "@/lib/practice-area";

/* Appeal numbers follow the 1xxx / 8xxx / 9xxx domain convention (leading
 * digit) from CLAUDE.md, plus the post-reform serial-length rule:
 *   4-digit serial → ערר  (appeal)             e.g. "1230-04-26"
 *   5-digit serial → בל"מ (extension-of-time)   e.g. "85074-09-24"
 *
 * New cases MUST carry the month — the canonical serial-month-year form
 * NNNN[N]-MM-YY (X1-identifiers spec §1; chair procedure 2026-06-11). The
 * legacy NNNN-YY (no-month) form is still tolerated by backend lookup for
 * historical records, but is no longer accepted when opening a new case.
 *
 * Slashes are deliberately forbidden: FastAPI path routing can't capture
 * a `/` inside a {case_number} segment even when URL-encoded as %2F, so
 * any case with a slash becomes unreachable at
 * GET /api/cases/{case_number}/details. */
const caseNumberRe = /^[1-9]\d{3,4}-\d{2}-\d{2}$/;

/* Serial digit-count: leading numeric group before month/year (4 = ערר,
 * 5 = בל"מ). Returns 0 when no serial is present. */
const caseSerialLen = (n: string): number =>
  n.trim().match(/^(\d{4,5})/)?.[1].length ?? 0;

const hebrewPartyRe = /[\u0590-\u05FFA-Za-z]/;

const dateString = z
  .string()
  .trim()
  .refine((v) => v === "" || /^\d{4}-\d{2}-\d{2}$/.test(v), {
    message: "תאריך חייב להיות בפורמט YYYY-MM-DD",
  });

// GAP-51: outcome = 3 canonical values only. "betterment_levy" is a
// practice_area (selected in its own field), not an outcome.
export const expectedOutcomes = [
  { value: "",                   label: "— לא נקבע —" },
  { value: "rejection",          label: "דחייה" },
  { value: "partial_acceptance", label: "קבלה חלקית" },
  { value: "full_acceptance",    label: "קבלה מלאה" },
] as const;

/* proceeding_type — distinguishes a regular appeal (ערר) from an
 * extension-of-time request (בל"מ). The same case_number can exist as
 * both, so this is a separate axis from appeal_subtype/practice_area. */
export const proceedingTypes = [
  { value: "ערר",  label: "ערר" },
  { value: 'בל"מ', label: 'בל"מ' },
] as const;
export type ProceedingType = (typeof proceedingTypes)[number]["value"];

export const caseCreateSchema = z.object({
  case_number: z
    .string()
    .trim()
    .min(1, "שדה חובה")
    .regex(
      caseNumberRe,
      'פורמט: מספר-חודש-שנה. 4 ספרות = ערר (1230-04-26), 5 ספרות = בל"מ (85074-09-24)',
    ),
  title: z.string().trim().min(3, "כותרת קצרה מדי").max(200, "כותרת ארוכה מדי"),
  appellants: z
    .array(z.string().trim().min(1).refine((v) => hebrewPartyRe.test(v), "שם לא תקין"))
    .min(1, "חייב להיות לפחות עורר אחד"),
  respondents: z
    .array(z.string().trim().min(1).refine((v) => hebrewPartyRe.test(v), "שם לא תקין"))
    .min(1, "חייב להיות לפחות משיב אחד"),
  subject: z.string().trim().max(500),
  property_address: z.string().trim().max(200),
  permit_number: z.string().trim().max(100),
  hearing_date: dateString,
  notes: z.string().trim().max(2000),
  expected_outcome: z.enum(
    expectedOutcomes.map((o) => o.value) as [string, ...string[]],
  ),
  practice_area: z.enum([
    "appeals_committee",
    "national_insurance",
    "labor_law",
  ] as const satisfies readonly PracticeArea[]),
  appeal_subtype: z.enum([
    "building_permit",
    "betterment_levy",
    "compensation_197",
    "extension_request_building_permit",
    "extension_request_betterment_levy",
    "extension_request_compensation",
    "unknown",
  ] as const satisfies readonly AppealSubtype[]),
  proceeding_type: z.enum(["ערר", 'בל"מ'] as const),
}).superRefine((d, ctx) => {
  /* Post-reform rule: a 5-digit serial IS a בל"מ. One-directional — a
   * 4-digit serial may still be a legacy בל"מ, so we don't force ערר. */
  if (caseSerialLen(d.case_number) === 5 && d.proceeding_type !== 'בל"מ') {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ["proceeding_type"],
      message: 'מספר בן 5 ספרות הוא תיק בל"מ — סוג התיק חייב להיות בל"מ',
    });
  }
});

export type CaseCreateInput = z.infer<typeof caseCreateSchema>;

/* Update schema — all fields optional so the PUT can be used for a
 * single-field edit. Empty strings are tolerated (they mean "no change"
 * in the Pydantic model). */
export const caseUpdateSchema = z.object({
  title:            z.string().trim().max(200).optional(),
  subject:          z.string().trim().max(500).optional(),
  notes:            z.string().trim().max(2000).optional(),
  hearing_date:     dateString.optional(),
  decision_date:    dateString.optional(),
  expected_outcome: z
    .enum(expectedOutcomes.map((o) => o.value) as [string, ...string[]])
    .optional(),
  status:           z.string().optional(),
  appellants: z
    .array(z.string().trim().min(1).refine((v) => hebrewPartyRe.test(v), "שם לא תקין"))
    .optional(),
  respondents: z
    .array(z.string().trim().min(1).refine((v) => hebrewPartyRe.test(v), "שם לא תקין"))
    .optional(),
  property_address: z.string().trim().max(200).optional(),
  permit_number:    z.string().trim().max(100).optional(),
  proceeding_type:  z.enum(["ערר", 'בל"מ'] as const).optional(),
});

export type CaseUpdateInput = z.infer<typeof caseUpdateSchema>;
