import type { Freight } from "@edr/types";
import { DeepPartial, Path } from "react-hook-form";
import * as z from "zod";

export const STEPS = [
  { id: 0, label: "Operation Type", short: "Operation" },
  { id: 1, label: "Contract Type", short: "Contract" },
  { id: 2, label: "Service Type & Mile", short: "Service" },
  { id: 3, label: "Cargo Details", short: "Cargo" },
  { id: 4, label: "Route", short: "Route" },
  { id: 5, label: "Estimated Date", short: "Schedule" },
  { id: 6, label: "Documents", short: "Documents" },
  { id: 7, label: "Review & Submit", short: "Submit" },
] as const;

export const OPERATION_TYPES = [
  "import",
  "export",
  "intercity",
  // Freight-forwarder variants: same trade direction as import/export but the
  // booking is stamped to the company's freight_forwarder profile instead of a
  // direct importer/exporter profile.
  "import_ff",
  "export_ff",
] as const;
export type OperationType = (typeof OPERATION_TYPES)[number];

/**
 * Shipment documents collected during booking creation. The fileKeys mirror
 * `REQUIRED_DOC_FIELDS` in BookingDetailPage/constants.ts.
 */
const DOC_SETTING_TS = "2024-01-01T00:00:00.000Z";

function docField(
  fileKey: string,
  fileLabel: string,
  order: number,
): Freight.IFileUploadField {
  return {
    id: fileKey,
    settingId: "booking_documents",
    createdAt: DOC_SETTING_TS,
    updatedAt: DOC_SETTING_TS,
    deletedAt: null,
    fileKey,
    fileLabel,
    helpText: null,
    isRequired: true,
    isMultiple: false,
    maxFiles: 1,
    allowedExtensions: ["pdf", "jpg", "jpeg", "png"],
    maxSizeMb: 10,
    order,
  };
}

export const BOOKING_DOCS_SETTING: Freight.IFileUploadSetting = {
  id: "booking_documents",
  createdAt: DOC_SETTING_TS,
  updatedAt: DOC_SETTING_TS,
  deletedAt: null,
  code: "booking_documents",
  label: "Booking Documents",
  description:
    "Attach all four required shipment documents before submitting your booking.",
  entity: "booking",
  fields: [
    docField("commercial_invoice", "Commercial Invoice", 1),
    docField("packing_list", "Packing List", 2),
    docField("certificate_of_origin", "Certificate of Origin", 3),
    docField("letter_of_credit", "Letter of Credit / LC", 4),
  ],
};

export type BookingDocuments = Record<string, File | File[] | null>;

export const PAYMENT_CURRENCIES = ["USD", "ETB"] as const;
export type PaymentCurrency = (typeof PAYMENT_CURRENCIES)[number];

export const PAYMENT_CURRENCY_OPTIONS: Array<{
  value: PaymentCurrency;
  label: string;
  description: string;
}> = [
  {
    value: "USD",
    label: "USD",
    description: "US Dollar — international pricing and invoicing.",
  },
  {
    value: "ETB",
    label: "ETB",
    description: "Ethiopian Birr — local pricing and invoicing.",
  },
];

export const BOOKING_TYPES = ["one_time", "general_contract"] as const;
export type BookingTypeOption = (typeof BOOKING_TYPES)[number];

export const bookingFormSchema = z
  .object({
    // Operation the booking is for, gated by the company's onboarded profiles.
    // Drives trade direction (import/export → IMPORT/EXPORT; intercity → DOMESTIC)
    // and the active company profile the booking is stamped to.
    operationType: z.enum(OPERATION_TYPES, "Select an operation type."),
    // One-time booking vs. a general contract (umbrella, drawn down by orders).
    bookingType: z.enum(BOOKING_TYPES).default("one_time"),
    contractType: z.enum(["new", "renewal"], "Select a contract type."),
    previousContractRef: z.string(),
    serviceTypeId: z.string("Select a service type."),
    paymentCurrency: z.enum(PAYMENT_CURRENCIES, "Select a payment currency."),

    firstMile: z
      .object({
        enabled: z.boolean().default(false),
        pickUpAddress: z.string(),
        // Coordinates resolved by the map picker (geocode/search or pin drop).
        lat: z.number().nullable().default(null),
        lng: z.number().nullable().default(null),
      })
      .refine((data) => !(data.enabled && !data.pickUpAddress.trim()), {
        message: "Select the pick-up location on the map.",
        path: ["pickUpAddress"],
      }),
    lastMile: z
      .object({
        enabled: z.boolean().default(false),
        deliveryAddress: z.string(),
        lat: z.number().nullable().default(null),
        lng: z.number().nullable().default(null),
      })
      .refine((data) => !(data.enabled && !data.deliveryAddress.trim()), {
        message: "Select the delivery location on the map.",
        path: ["deliveryAddress"],
      }),
    equipmentReturn: z
      .enum(["with_return", "without_return"])
      .default("with_return"),
    customsClearingEnabled: z.boolean().default(false),
    // Required only when customs clearing is enabled (validated in superRefine).
    customsClearingAgent: z.string().default(""),
    originYard: z.string().min(1, "Select an origin yard."),
    destinationYard: z.string().min(1, "Select a destination yard."),
    // Quantity reserved on the PRIMARY route of a GENERAL contract, in the unit
    // of the selected commodity (items vs tons). Customers enter it explicitly in
    // the route step so the primary route reads consistently with the extra
    // routes below. Ignored for one-time bookings; for containers the value is
    // derived from the container count instead (see buildApiPayload).
    primaryRouteQuantity: z.string().default(""),
    // Additional routes for a GENERAL contract (the primary origin/destination
    // above is route #1). Each adds another (origin, destination, quantity) pool.
    // Ignored for one-time bookings.
    extraRoutes: z
      .array(
        z.object({
          originYard: z.string(),
          destinationYard: z.string(),
          quantity: z.string(),
          // Road distance for this route; used to bill road (truck) orders.
          km: z.string().default(""),
        }),
      )
      .default([]),
    // Day-level pool: the customer selects only a DAY. The batch engine assigns
    // the specific train later, so no trainScheduleId is collected here.
    // Optional in the base schema — required for one-time bookings via the
    // superRefine below; general contracts pick the date per order instead.
    scheduledDate: z.string().default(""),
    cargoType: z.enum(["container", "bulk"], "Select a cargo type."),
    cargoWeight: z.string(),
    cargoTypePath: z.array(z.string()).default([]),
    cargoFreeText: z.string(),
    isHazardous: z.boolean(),
    isRefrigerated: z.boolean(),
    containers: z.array(
      z.object({
        type: z.enum(["20ft", "40ft"]),
        containerType: z.string().min(1, "Select a container type."),
        qty: z
          .string()
          .refine((q) => q.length !== 0, "Quantity is required.")
          .refine((q) => !isNaN(+q), "Enter a valid Number")
          .refine((qty) => Number(qty) >= 1, "Must be greater than 0"),
        // Weight (VGM) is NOT collected at the wizard — it is captured later in
        // operations. Kept optional so existing payload code stays valid.
        vgm: z.string().default("0"),
      }),
    ),
    // Consolidation is system-managed, not a customer choice: the backend
    // consolidates partial-wagon container bookings automatically, derived from
    // the container quantities. The customer neither sees nor toggles it.
    documents: z.record(z.string(), z.any()).default({}),
    notes: z.string(),
  })
  .refine(
    (data) =>
      !(data.contractType === "renewal" && !data.previousContractRef.trim()),
    {
      message: "Enter a previous contract reference.",
      path: ["previousContractRef"],
    },
  )
  .refine((data) => data.originYard !== "", {
    message: "Select an origin yard.",
    path: ["originYard"],
  })
  .refine((data) => data.destinationYard !== "", {
    message: "Select a destination yard.",
    path: ["destinationYard"],
  })
  .refine(
    (data) =>
      !(
        data.originYard &&
        data.destinationYard &&
        data.originYard === data.destinationYard
      ),
    {
      message: "Destination must be different from origin.",
      path: ["destinationYard"],
    },
  )
  .refine(
    (data) => {
      // General contracts capture bulk quantity per route (primaryRouteQuantity),
      // not via the cargo-step cargoWeight — so only validate it for one-time
      // bulk bookings.
      if (data.cargoType !== "bulk") return true;
      if (data.bookingType === "general_contract") return true;
      const quantity = Number(data.cargoWeight);
      return !!data.cargoWeight && !Number.isNaN(quantity) && quantity > 0;
    },
    { message: "Enter a quantity greater than 0.", path: ["cargoWeight"] },
  )
  .refine(
    (data) => !(data.cargoType === "container" && data.containers.length === 0),
    { message: "Add at least one container.", path: ["containers"] },
  )
  .superRefine((data, ctx) => {
    // One-time bookings must pick a shipment date; general contracts must not.
    if (data.bookingType !== "general_contract" && !data.scheduledDate.trim()) {
      ctx.addIssue({
        code: "custom",
        path: ["scheduledDate"],
        message: "Select a shipment date.",
      });
    }
    // Customs clearing agent is required once the customs service is enabled.
    if (data.customsClearingEnabled && !data.customsClearingAgent.trim()) {
      ctx.addIssue({
        code: "custom",
        path: ["customsClearingAgent"],
        message: "Enter the customs clearing agent.",
      });
    }
    if (data.cargoType === "bulk") {
      if (!data.cargoTypePath[0]) {
        ctx.addIssue({
          code: "custom",
          path: ["cargoTypePath"],
          message: "Select a Cargo type.",
        });
      }
    }
    // General contracts reserve quantity per route. The primary route's quantity
    // is entered in the route step; containers derive it from the container
    // count, so only bulk cargo requires it here.
    if (data.bookingType === "general_contract" && data.cargoType === "bulk") {
      const qty = Number(data.primaryRouteQuantity);
      if (!data.primaryRouteQuantity || Number.isNaN(qty) || qty <= 0) {
        ctx.addIssue({
          code: "custom",
          path: ["primaryRouteQuantity"],
          message: "Enter a quantity greater than 0.",
        });
      }
    }
    if (data.cargoType === "container") {
      data.containers.forEach((c, i) => {
        if (!c.qty || +c.qty < 1) {
          ctx.addIssue({
            code: "custom",
            path: ["containers", i, "qty"],
            message: "Enter at least 1 container.",
          });
        }
      });
    }
  });

export type BookingFormValues = z.infer<typeof bookingFormSchema>;
export type BookingFormInputValues = z.input<typeof bookingFormSchema>;

export const initialBookingFormValues: DeepPartial<BookingFormValues> = {
  bookingType: "one_time",
  previousContractRef: "",

  serviceTypeId: "",
  paymentCurrency: "USD",
  firstMile: {
    enabled: false,
    pickUpAddress: "",
    lat: null,
    lng: null,
  },
  lastMile: {
    enabled: false,
    deliveryAddress: "",
    lat: null,
    lng: null,
  },
  equipmentReturn: "with_return",
  customsClearingEnabled: false,
  customsClearingAgent: "",
  originYard: "",
  destinationYard: "",
  primaryRouteQuantity: "",
  extraRoutes: [],
  scheduledDate: "",
  cargoWeight: "",
  cargoTypePath: [],
  cargoFreeText: "",
  isHazardous: false,
  isRefrigerated: false,
  containers: [{ type: "20ft", containerType: "", qty: "1", vgm: "0" }],
  documents: {},
  notes: "",
};

export const stepFields: Record<number, Array<Path<BookingFormValues>>> = {
  0: ["operationType"],
  1: ["bookingType", "contractType", "previousContractRef"],
  2: [
    "serviceTypeId",
    "paymentCurrency",
    "equipmentReturn",
    "customsClearingEnabled",
    "customsClearingAgent",
    // First/last-mile pickup & delivery locations are captured inline in the
    // service step, right under each trucking toggle.
    "firstMile",
    "lastMile",
  ],
  3: ["cargoType", "cargoWeight", "cargoTypePath", "containers"],
  4: [
    "originYard",
    "destinationYard",
    "primaryRouteQuantity",
    "extraRoutes",
    "isHazardous",
    "isRefrigerated",
  ],
  5: ["scheduledDate"],
  6: ["documents"],
  7: ["notes"],
};

export interface ContainerConfig {
  type: "20ft" | "40ft";
  containerType: string;
  qty: string;
  // Optional: VGM is captured later in operations, not at the wizard, and the
  // form schema defaults it — so the watched input shape has it as optional.
  vgm?: string;
}

export interface WagonConfig {
  type: "20ft" | "40ft";
}

/**
 * Derive the trade direction from the origin/destination yard countries.
 *
 * Mirrors the backend's `deriveTradeDirection` exactly so the value the portal
 * sends always matches what the API re-derives (the API rejects mismatches):
 *   - origin in Djibouti                      → IMPORT
 *   - destination in Djibouti (origin not)    → EXPORT
 *   - everything else (e.g. Ethiopia↔Ethiopia)→ DOMESTIC
 *
 * Returns null only while a yard is still unselected, so the UI can wait.
 */
export function getRouteDirection(
  origin: Freight.BookingReferenceYard | null | undefined,
  dest: Freight.BookingReferenceYard | null | undefined,
): Freight.ScheduleTradeDirection | null {
  if (!origin || !dest) return null;

  const originCountry = origin.country?.trim();
  const destCountry = dest.country?.trim();

  if (originCountry === "Djibouti") {
    return "IMPORT";
  }
  if (destCountry === "Djibouti" && originCountry !== "Djibouti") {
    return "EXPORT";
  }
  return "DOMESTIC";
}

/**
 * Operations a company may book, derived from its onboarded profile types.
 *
 *   - pure freight forwarder           → import, export, intercity
 *     (these run as FF: the booking is stamped to the freight_forwarder profile)
 *   - importer                         → import, intercity
 *   - exporter                         → export, intercity
 *   - importer + exporter              → import, export, intercity
 *   - importer (+/- exporter) + FF     → direct import/export PLUS the matching
 *     "as FF" variants, so the company can book either directly or as a forwarder
 *
 * Intercity (DOMESTIC) is always available to any customer-side profile.
 */
export function allowedOperationsForProfiles(
  profileTypes: string[],
): OperationType[] {
  const has = (t: string) => profileTypes.includes(t);
  const isForwarder = has("freight_forwarder") || has("dj_freight_forwarder");
  const isImporter = has("importer");
  const isExporter = has("exporter");
  const isDirect = isImporter || isExporter;

  const ops = new Set<OperationType>();

  // Direct importer/exporter capabilities.
  if (isImporter) ops.add("import");
  if (isExporter) ops.add("export");

  if (isForwarder) {
    if (isDirect) {
      // Mixed: keep the direct options above and add explicit "as FF" variants
      // so the customer can disambiguate which profile the booking belongs to.
      ops.add("import_ff");
      ops.add("export_ff");
    } else {
      // Pure forwarder: shows plain Import/Export/Intercity, but these run on the
      // freight_forwarder profile (see operationToProfileType).
      ops.add("import");
      ops.add("export");
    }
  }

  // Any customer-side profile can also run domestic (intercity).
  if (isForwarder || isDirect) ops.add("intercity");

  // Preserve a stable display order.
  return OPERATION_TYPES.filter((o) => ops.has(o));
}

/**
 * Whether this operation runs on the freight_forwarder profile. True for the
 * explicit FF variants, and for plain import/export when the company is a pure
 * forwarder (no direct importer/exporter profile).
 */
export function isForwarderOperation(
  op: OperationType,
  profileTypes: string[],
): boolean {
  if (op === "import_ff" || op === "export_ff") return true;
  const has = (t: string) => profileTypes.includes(t);
  const isForwarder = has("freight_forwarder") || has("dj_freight_forwarder");
  const isDirect = has("importer") || has("exporter");
  if ((op === "import" || op === "export") && isForwarder && !isDirect) {
    return true;
  }
  return false;
}

/** Trade direction the backend will derive for a given operation type. */
export function operationToTradeDirection(
  op: OperationType,
): Freight.ScheduleTradeDirection {
  if (op === "import" || op === "import_ff") return "IMPORT";
  if (op === "export" || op === "export_ff") return "EXPORT";
  return "DOMESTIC";
}

/**
 * The company_profile type a booking for this operation should be stamped to.
 * FF variants (and a pure forwarder's plain import/export) → freight_forwarder.
 */
export function operationToProfileType(
  op: OperationType,
  profileTypes: string[] = [],
): string {
  if (op === "import_ff" || op === "export_ff") return "freight_forwarder";
  if (isForwarderOperation(op, profileTypes)) return "freight_forwarder";
  if (op === "import") return "importer";
  if (op === "export") return "exporter";
  return "freight_forwarder";
}

export function calcWagons(containers: ContainerConfig[]) {
  const Ft20Wagons = containers
    .filter((c) => c.type === "20ft")
    .reduce((sum, c) => sum + Number(c.qty), 0);
  let hasOddUnit = Ft20Wagons % 2 === 1;

  return {
    hasOddUnit,
    ft20Wagons: Ft20Wagons,
  };
}
