import { z } from "zod/v4";
import { LIMITS } from "@/lib/limits";
import { ACTIVITY_CATEGORIES } from "@/lib/activity-category";
import { VIBES } from "@/lib/vibe";
import type { ActivityCategory } from "@/app/generated/prisma/enums";
import {
  isTripDepartureDayPast,
  tripStoredInstantFromYmd,
} from "@/lib/trip-dates";
import { isValidTimeFormat, timeToMinutes } from "@/lib/itinerary";

/**
 * Foto trip sekarang adalah file yang diunggah ke server sendiri, bukan URL
 * eksternal. Nilai yang valid hanya path terkelola `/api/trip-images/<hex>.webp`
 * yang dihasilkan route upload — regex ini sengaja ketat supaya URL arbitrary
 * (yang dulu sering tidak reachable dari server) tidak bisa lolos lagi.
 */
export const tripImageUrlsSchema = z
  .array(
    z
      .string()
      .trim()
      .regex(
        /^\/api\/trip-images\/[a-f0-9]{32}\.webp$/,
        "Foto trip tidak valid — silakan unggah ulang fotonya"
      )
  )
  .max(LIMITS.MAX_IMAGE_URLS, `Maksimal ${LIMITS.MAX_IMAGE_URLS} foto`);

export const itineraryItemSchema = z
  .object({
    day: z.coerce
      .number()
      .int("Nomor hari tidak valid")
      .min(1, "Nomor hari minimal 1")
      .max(
        LIMITS.MAX_ITINERARY_DAYS,
        `Maksimal ${LIMITS.MAX_ITINERARY_DAYS} hari`
      ),
    startTime: z
      .string()
      .trim()
      .refine(isValidTimeFormat, "Format jam mulai harus HH:mm"),
    endTime: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z
        .string()
        .refine(isValidTimeFormat, "Format jam selesai harus HH:mm")
        .optional()
    ),
    activity: z
      .string()
      .trim()
      .min(1, "Deskripsi aktivitas harus diisi")
      .max(
        LIMITS.MAX_ITINERARY_ACTIVITY_LENGTH,
        `Aktivitas maksimal ${LIMITS.MAX_ITINERARY_ACTIVITY_LENGTH} karakter`
      ),
  })
  .superRefine((data, ctx) => {
    if (
      data.endTime &&
      timeToMinutes(data.endTime) < timeToMinutes(data.startTime)
    ) {
      ctx.addIssue({
        code: "custom",
        message: "Jam selesai tidak boleh sebelum jam mulai",
        path: ["endTime"],
      });
    }
  });

export const itineraryItemsSchema = z
  .array(itineraryItemSchema)
  .max(
    LIMITS.MAX_ITINERARY_ITEMS,
    `Maksimal ${LIMITS.MAX_ITINERARY_ITEMS} aktivitas total`
  )
  .superRefine((items, ctx) => {
    if (items.length === 0) return;
    const days = [...new Set(items.map((i) => i.day))].sort((a, b) => a - b);
    for (let i = 0; i < days.length; i++) {
      if (days[i] !== i + 1) {
        ctx.addIssue({
          code: "custom",
          message:
            "Nomor hari harus berurutan mulai dari Hari 1 (tidak boleh lompat)",
          path: [0, "day"],
        });
        return;
      }
    }
  });

export const createTripSchema = z
  .object({
    category: z.enum(
      ACTIVITY_CATEGORIES as [ActivityCategory, ...ActivityCategory[]],
      { message: "Kategori aktivitas tidak valid" }
    ),
    title: z
      .string()
      .trim()
      .min(3, "Judul minimal 3 karakter")
      .max(
        LIMITS.MAX_TITLE_LENGTH,
        `Judul maksimal ${LIMITS.MAX_TITLE_LENGTH} karakter`
      ),
    description: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z
        .string()
        .max(
          LIMITS.MAX_DESCRIPTION_LENGTH,
          `Deskripsi maksimal ${LIMITS.MAX_DESCRIPTION_LENGTH} karakter`
        )
        .optional()
    ),
    destination: z
      .string()
      .trim()
      .min(2, "Destinasi harus diisi")
      .max(
        LIMITS.MAX_DESTINATION_LENGTH,
        `Destinasi maksimal ${LIMITS.MAX_DESTINATION_LENGTH} karakter`
      ),
    location: z
      .string()
      .trim()
      .min(2, "Lokasi harus diisi")
      .max(
        LIMITS.MAX_LOCATION_LENGTH,
        `Lokasi maksimal ${LIMITS.MAX_LOCATION_LENGTH} karakter`
      ),
    date: z
      .string()
      .refine((val) => !Number.isNaN(Date.parse(val)), "Tanggal berangkat tidak valid"),
    endDate: z
      .string()
      .optional()
      .refine(
        (val) => !val || !Number.isNaN(Date.parse(val)),
        "Tanggal pulang tidak valid"
      ),
    maxParticipants: z.coerce
      .number()
      .int("Jumlah peserta harus bilangan bulat")
      .min(
        LIMITS.MIN_PARTICIPANTS,
        `Minimal ${LIMITS.MIN_PARTICIPANTS} peserta`
      )
      .max(
        LIMITS.MAX_PARTICIPANTS,
        `Maksimal ${LIMITS.MAX_PARTICIPANTS} peserta`
      ),
    price: z.coerce
      .number()
      .int("Harga harus bilangan bulat (tanpa desimal)")
      .min(0, "Harga tidak valid")
      .max(
        LIMITS.MAX_PRICE_IDR,
        `Harga maksimal Rp ${LIMITS.MAX_PRICE_IDR.toLocaleString("id-ID")}`
      ),
    meetingPoint: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z
        .string()
        .max(
          LIMITS.MAX_MEETING_POINT_LENGTH,
          `Meeting point maksimal ${LIMITS.MAX_MEETING_POINT_LENGTH} karakter`
        )
        .optional()
    ),
    whatsIncluded: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z
        .string()
        .max(
          LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH,
          `Bagian 'Termasuk' maksimal ${LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH} karakter`
        )
        .optional()
    ),
    whatsExcluded: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z
        .string()
        .max(
          LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH,
          `Bagian 'Tidak termasuk' maksimal ${LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH} karakter`
        )
        .optional()
    ),
    vibe: z.preprocess(
      (val) => {
        if (val == null) return undefined;
        const s = String(val).trim();
        return s === "" ? undefined : s;
      },
      z.enum([...VIBES]).optional()
    ),
  })
  .superRefine((data, ctx) => {
    const dep = tripStoredInstantFromYmd(data.date);
    if (!Number.isNaN(dep.getTime()) && isTripDepartureDayPast(dep)) {
      ctx.addIssue({
        code: "custom",
        message: "Tanggal berangkat tidak boleh di masa lalu",
        path: ["date"],
      });
    }
    if (data.endDate) {
      const startY = data.date.slice(0, 10);
      const endY = data.endDate.slice(0, 10);
      if (endY < startY) {
        ctx.addIssue({
          code: "custom",
          message: "Tanggal pulang tidak boleh sebelum tanggal berangkat",
          path: ["endDate"],
        });
      }
    }
  });

export type CreateTripInput = z.infer<typeof createTripSchema>;
