import { z } from "zod";
import { assetStatusEnum } from "@/db/schema";
import { uuidSchema, optionalText } from "./common";
import type { MerkmalDefinition } from "@/lib/merkmale/types";

/**
 * Zod-Schemas für Fahrzeuge (Workstream 7). `funkrufname` ist eine SPALTE
 * (kein Merkmal). `status` über das DB-Enum `asset_status` (Single Source of
 * Truth). Leere Optionalfelder werden zu `undefined` normalisiert.
 */

export const assetStatusSchema = z.enum(assetStatusEnum.enumValues);

const optionalUuid = z
  .string()
  .trim()
  .optional()
  .transform((v) => (v === undefined || v === "" ? undefined : v))
  .refine((v) => v === undefined || uuidSchema.safeParse(v).success, {
    message: "Ungültige ID.",
  });

export const vehicleBaseSchema = z.object({
  name: z.string().trim().min(1, { message: "Fahrzeugname ist Pflicht." }),
  funkrufname: optionalText,
  notiz: optionalText,
  templateId: optionalUuid,
});

export type VehicleBaseInput = z.infer<typeof vehicleBaseSchema>;

export const vehicleCreateSchema = vehicleBaseSchema;
export const vehicleUpdateSchema = z.object({
  id: uuidSchema,
  ...vehicleBaseSchema.shape,
});
export type VehicleUpdateInput = z.infer<typeof vehicleUpdateSchema>;

export const vehicleStatusSchema = z.object({
  id: uuidSchema,
  status: assetStatusSchema,
});
export type VehicleStatusInput = z.infer<typeof vehicleStatusSchema>;

export const vehicleIdSchema = z.object({ id: uuidSchema });

/**
 * Baut ein typgerechtes Zod-Schema für eine Liste von Merkmal-Werten, abgeleitet
 * aus den angebotenen `MerkmalDefinition`s (Vorlage/Kategorie). Pflichtmerkmale
 * müssen einen Wert haben; `enum`-Werte sind auf die Optionen beschränkt;
 * Zahlen werden gecoerced. Werte zu nicht angebotenen `merkmalId`s sind
 * unzulässig (kein Schmuggeln fremder Merkmale).
 *
 * REIN: nimmt Definitionen als Parameter, damit es ohne DB testbar ist.
 */
export function buildMerkmalValuesSchema(definitionen: MerkmalDefinition[]) {
  const byId = new Map(definitionen.map((d) => [d.merkmalId, d]));

  const single = z
    .object({
      merkmalId: uuidSchema,
      num: z.unknown().optional(),
      text: z.unknown().optional(),
      bool: z.unknown().optional(),
    })
    .superRefine((raw, ctx) => {
      const def = byId.get(raw.merkmalId);
      if (!def) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["merkmalId"],
          message: "Unbekanntes Merkmal.",
        });
        return;
      }

      if (def.typ === "number") {
        const v = parseNumber(raw.num);
        if (v === undefined) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["num"],
            message: `„${def.name}“ muss eine Zahl sein.`,
          });
        } else if (v === null && def.pflicht) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["num"],
            message: `„${def.name}“ ist Pflicht.`,
          });
        }
        return;
      }

      if (def.typ === "boolean") {
        const v = parseBool(raw.bool);
        if (def.pflicht && v == null) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["bool"],
            message: `„${def.name}“ ist Pflicht.`,
          });
        }
        return;
      }

      // text | enum
      const text = parseText(raw.text);
      if (def.pflicht && (text == null || text === "")) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["text"],
          message: `„${def.name}“ ist Pflicht.`,
        });
        return;
      }
      if (def.typ === "enum" && text != null && text !== "") {
        const erlaubt = def.optionen.map((o) => o.wert);
        if (!erlaubt.includes(text)) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["text"],
            message: `Ungültige Auswahl für „${def.name}“.`,
          });
        }
      }
    })
    .transform((raw) => {
      const def = byId.get(raw.merkmalId)!;
      if (def.typ === "number") {
        const v = parseNumber(raw.num);
        return { merkmalId: raw.merkmalId, num: v ?? null };
      }
      if (def.typ === "boolean") {
        return { merkmalId: raw.merkmalId, bool: parseBool(raw.bool) };
      }
      const text = parseText(raw.text);
      return { merkmalId: raw.merkmalId, text: text ?? null };
    });

  return z.array(single).superRefine((werte, ctx) => {
    // Vollständigkeit auf Array-Ebene: ein Pflichtmerkmal, das komplett fehlt
    // (kein Element mit gesetztem Wert), wird sonst von der Pro-Element-Prüfung
    // nicht erfasst. "Validieren, nicht vertrauen" (Querschnittsstandard 4).
    for (const def of definitionen) {
      if (!def.pflicht) continue;
      const hatWert = werte.some((w) => {
        if (w.merkmalId !== def.merkmalId) return false;
        if (def.typ === "number") return w.num != null;
        if (def.typ === "boolean") return w.bool != null;
        return w.text != null && w.text !== "";
      });
      if (!hatWert) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: `„${def.name}“ ist Pflicht.`,
        });
      }
    }
  });
}

/** `undefined` = ungültig (NaN), `null` = leer, sonst die Zahl. */
function parseNumber(v: unknown): number | null | undefined {
  if (v == null || v === "") return null;
  const n = typeof v === "number" ? v : Number(v);
  return Number.isFinite(n) ? n : undefined;
}

function parseBool(v: unknown): boolean | null {
  if (v == null || v === "") return null;
  if (typeof v === "boolean") return v;
  if (v === "true") return true;
  if (v === "false") return false;
  return null;
}

function parseText(v: unknown): string | null {
  if (v == null) return null;
  return typeof v === "string" ? v : String(v);
}
