import { DeepPartial, Path } from "react-hook-form";
import * as z from "zod";

// Shipment booking under a contract (doc §8.1, Path A customer). Captures the
// EXECUTION details the contract scope deliberately omits: a binding scheduled
// date, container quantities + per-unit numbers/seals/VGM, or bulk tonnage.
// Order: Route → Cargo Details → Schedule → Review. Cargo is captured BEFORE
// Schedule so the schedule step can offer only days that are feasible for that
// cargo (enough matching wagons + an open train with capacity).
export const SHIPMENT_STEPS = [
  { id: 0, label: "Route", short: "Route" },
  { id: 1, label: "Cargo Details", short: "Cargo" },
  { id: 2, label: "Schedule", short: "Schedule" },
  { id: 3, label: "Review", short: "Review" },
] as const;

export interface ShipmentValidationContext {
  isContainer: boolean;
  isHazardous: boolean;
  isReefer: boolean;
  /**
   * Contract was created with the empty-container return service
   * (equipment_return = WITH_RETURN, container freight only). Enables the
   * per-line "with return" quantity, validated like hazardous/reefer.
   */
  withReturnService?: boolean;
  unitOfMeasure?: "PER_TON" | "PER_ITEM";
  /**
   * Intercity (DOMESTIC) shipments ride a passing import/export train that
   * staff pick later, so no shipment day is chosen. Defaults to true.
   */
  requiresDate?: boolean;
}

// ISO 6346: 3-letter owner code + category id (U/J/Z) + 6-digit serial + check digit.
const ISO_CONTAINER_NUMBER_REGEX = /^[A-Z]{4}\d{7}$/;

const containerUnitSchema = z.object({
  containerNumber: z
    .string()
    .min(1, "Container number is required.")
    .refine(
      (v) => ISO_CONTAINER_NUMBER_REGEX.test(v.trim().toUpperCase()),
      "Enter a valid ISO container number (e.g. ABCD1234567).",
    ),
  sealNumber: z.string().default(""),
  vgmTons: z
    .string()
    .refine((v) => v.trim().length > 0, "VGM is required.")
    .refine((v) => !Number.isNaN(Number(v)) && Number(v) > 0, "Enter a valid VGM."),
  // Handling is per physical container, recorded next to its VGM. The line
  // totals below are derived from these.
  isHazardous: z.boolean().default(false),
  isReefer: z.boolean().default(false),
  isReturn: z.boolean().default(false),
});

const containerLineSchema = z.object({
  containerSize: z.enum(["20ft", "40ft"]),
  // 0 is allowed: the contract covers both sizes, so a booking that only needs
  // one size zeroes the other line out. At least one line must be ≥ 1
  // (enforced in the superRefine).
  quantity: z
    .string()
    .refine((v) => v.trim().length > 0, "Quantity is required.")
    .refine((v) => !Number.isNaN(Number(v)) && Number(v) >= 0, "Enter 0 or more."),
  hazardousQuantity: z.string().default("0"),
  reeferQuantity: z.string().default("0"),
  returnQuantity: z.string().default("0"),
  units: z.array(containerUnitSchema).default([]),
});

const shipmentFormBase = z.object({
  contractRouteId: z.string().default(""),
  scheduledDate: z.string().default(""),
  // The contract quotes in USD; the customer picks the billing currency for
  // THIS shipment. Intercity is forced to ETB (server-enforced too).
  paymentCurrency: z.enum(["USD", "ETB"]).default("USD"),
  // Container contracts only: return the empty container(s) to EDR after
  // unloading. Seeded from the contract's equipment return; bulk ignores it.
  withReturn: z.boolean().default(false),
  containers: z.array(containerLineSchema).default([]),
  // What the containers carry — captured per booking (moved off the contract).
  cargoDescription: z.string().default(""),
  cargoWeightTons: z.string().default(""),
  itemCount: z.string().default(""),
  bulkHazardousQuantity: z.string().default("0"),
  bulkReeferQuantity: z.string().default("0"),
  notes: z.string().default(""),
});

export function createShipmentFormSchema(ctx: ShipmentValidationContext) {
  return shipmentFormBase.superRefine((data, refineCtx) => {
    if ((ctx.requiresDate ?? true) && !data.scheduledDate.trim()) {
      refineCtx.addIssue({
        code: "custom",
        path: ["scheduledDate"],
        message: "Select a shipment date.",
      });
    }

    if (ctx.isContainer) {
      // Containerized cargo must say WHAT is inside — required per booking.
      if (!data.cargoDescription.trim()) {
        refineCtx.addIssue({
          code: "custom",
          path: ["cargoDescription"],
          message: "Describe the cargo carried in the containers.",
        });
      }
      // Both sizes are always in contract scope and a line can be 0 — but the
      // booking as a whole needs at least one container. Anchor the error on
      // the first line's quantity so it renders in the field.
      const totalQty = data.containers.reduce(
        (sum, l) => sum + Math.max(0, Number(l.quantity) || 0),
        0,
      );
      if (data.containers.length > 0 && totalQty < 1) {
        refineCtx.addIssue({
          code: "custom",
          path: ["containers", 0, "quantity"],
          message: "Book at least one container (either size).",
        });
      }
      // Container numbers must be unique within this shipment (front-end only —
      // the DB column is intentionally not unique). Duplicates block submit and
      // price generation since both run through this same schema validation.
      const numberCounts = new Map<string, number>();
      data.containers.forEach((line) => {
        line.units.forEach((u) => {
          const key = u.containerNumber.trim().toUpperCase();
          if (!key) return;
          numberCounts.set(key, (numberCounts.get(key) ?? 0) + 1);
        });
      });

      data.containers.forEach((line, i) => {
        const qty = Number(line.quantity || 0);
        if (qty >= 1 && line.units.length < qty) {
          refineCtx.addIssue({
            code: "custom",
            path: ["containers", i, "units"],
            message: `Enter details for all ${qty} container(s).`,
          });
        }
        line.units.forEach((u, j) => {
          const key = u.containerNumber.trim().toUpperCase();
          if (key && (numberCounts.get(key) ?? 0) > 1) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "units", j, "containerNumber"],
              message: "Duplicate container number in this shipment.",
            });
          }
        });
        if (ctx.isHazardous) {
          const h = Number(line.hazardousQuantity || 0);
          if (h < 0) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "hazardousQuantity"],
              message: "Enter a valid hazardous quantity.",
            });
          } else if (h > qty) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "hazardousQuantity"],
              message: `Can't exceed the ${qty} container(s) in this line.`,
            });
          }
        }
        if (ctx.isReefer) {
          const r = Number(line.reeferQuantity || 0);
          if (r < 0) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "reeferQuantity"],
              message: "Enter a valid refrigerated quantity.",
            });
          } else if (r > qty) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "reeferQuantity"],
              message: `Can't exceed the ${qty} container(s) in this line.`,
            });
          }
        }
        if (ctx.withReturnService) {
          const w = Number(line.returnQuantity || 0);
          if (w < 0) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "returnQuantity"],
              message: "Enter a valid return quantity.",
            });
          } else if (w > qty) {
            refineCtx.addIssue({
              code: "custom",
              path: ["containers", i, "returnQuantity"],
              message: `Can't exceed the ${qty} container(s) in this line.`,
            });
          }
        }
      });
    } else {
      const isPerItem = ctx.unitOfMeasure === "PER_ITEM";
      const bulkCap = isPerItem
        ? Number(data.itemCount || 0)
        : Number(data.cargoWeightTons || 0);

      // The bulk cargo amount itself: a positive magnitude. Without this a
      // negative (typed past the input's `min`) reaches the API unchecked.
      const bulkPath = isPerItem ? "itemCount" : "cargoWeightTons";
      if (Number.isNaN(bulkCap) || bulkCap <= 0) {
        refineCtx.addIssue({
          code: "custom",
          path: [bulkPath],
          message: "Enter a quantity greater than 0.",
        });
      }

      const boundBulkPortion = (
        on: boolean,
        raw: string,
        path: "bulkHazardousQuantity" | "bulkReeferQuantity",
        noun: string,
      ) => {
        if (!on) return;
        const v = Number(raw || 0);
        if (raw && !Number.isNaN(v) && bulkCap > 0 && v > bulkCap) {
          refineCtx.addIssue({
            code: "custom",
            path: [path],
            message: `Can't exceed the cargo quantity (${bulkCap}).`,
          });
        } else if (on && raw && !Number.isNaN(v) && v < 0) {
          refineCtx.addIssue({
            code: "custom",
            path: [path],
            message: `Enter a valid ${noun} quantity.`,
          });
        }
      };

      boundBulkPortion(
        ctx.isHazardous,
        data.bulkHazardousQuantity,
        "bulkHazardousQuantity",
        "hazardous",
      );
      boundBulkPortion(
        ctx.isReefer,
        data.bulkReeferQuantity,
        "bulkReeferQuantity",
        "refrigerated",
      );
    }
  });
}

/** Default schema for type inference; runtime uses createShipmentFormSchema. */
export const shipmentFormSchema = createShipmentFormSchema({
  isContainer: false,
  isHazardous: false,
  isReefer: false,
});

export type ShipmentFormValues = z.infer<typeof shipmentFormSchema>;
export type ShipmentFormInputValues = z.input<typeof shipmentFormSchema>;

export const initialShipmentFormValues: DeepPartial<ShipmentFormValues> = {
  contractRouteId: "",
  scheduledDate: "",
  withReturn: false,
  containers: [],
  cargoDescription: "",
  cargoWeightTons: "",
  itemCount: "",
  bulkHazardousQuantity: "0",
  bulkReeferQuantity: "0",
  notes: "",
};

export const shipmentStepFields: Record<
  number,
  Array<Path<ShipmentFormValues>>
> = {
  0: ["contractRouteId"],
  1: [
    "containers",
    "cargoDescription",
    "cargoWeightTons",
    "itemCount",
    "bulkHazardousQuantity",
    "bulkReeferQuantity",
    "withReturn",
  ],
  2: ["scheduledDate"],
  3: ["notes"],
};
