"use client";

import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import {
  ArrowLeft,
  ArrowRight,
  Check,
  X,
  CircleAlert,
  Users,
} from "lucide-react";
import { DateRangeField, TimeField } from "@/components/shared/date-picker";
import { createTripAction } from "@/features/trip/actions";
import { TripImageUpload } from "@/features/trip/components/trip-image-upload";
import { formatLocalCalendarYmd } from "@/lib/trip-dates";
import { ACTIVITY_CATEGORIES, categoryMeta } from "@/lib/activity-category";
import { VIBES, vibeMeta } from "@/lib/vibe";
import { LIMITS } from "@/lib/limits";
import {
  isValidTimeFormat,
  timeToMinutes,
  type ItineraryItemInput,
} from "@/lib/itinerary";
import type { ActivityCategory, Vibe } from "@/app/generated/prisma/enums";

type Step = 1 | 2 | 3 | 4;

type DraftItineraryItem = {
  startTime: string;
  endTime: string;
  activity: string;
};

type ItineraryDays = DraftItineraryItem[][];

type FormState = {
  category: ActivityCategory;
  vibe: Vibe | null;
  title: string;
  destination: string;
  location: string;
  description: string;
  meetingPoint: string;
  itineraryDays: ItineraryDays;
  whatsIncluded: string;
  whatsExcluded: string;
  imageUrls: string[];
  maxParticipants: string;
  priceDisplay: string;
};

const INITIAL_STATE: FormState = {
  category: "HIKING",
  vibe: null,
  title: "",
  destination: "",
  location: "",
  description: "",
  meetingPoint: "",
  itineraryDays: [],
  whatsIncluded: "",
  whatsExcluded: "",
  imageUrls: [],
  maxParticipants: "",
  priceDisplay: "",
};

function flattenItinerary(days: ItineraryDays): ItineraryItemInput[] {
  const out: ItineraryItemInput[] = [];
  days.forEach((dayItems, dayIdx) => {
    dayItems.forEach((item) => {
      if (
        !item.startTime &&
        !item.endTime &&
        item.activity.trim().length === 0
      ) {
        return;
      }
      out.push({
        day: dayIdx + 1,
        startTime: item.startTime,
        endTime: item.endTime ? item.endTime : null,
        activity: item.activity,
      });
    });
  });
  return out;
}

const STEPS: { id: Step; label: string; subtitle: string }[] = [
  { id: 1, label: "Vibe", subtitle: "Jenis aktivitas & vibe" },
  { id: 2, label: "Info", subtitle: "Judul, destinasi & lokasi" },
  { id: 3, label: "Detail", subtitle: "Itinerary & foto" },
  { id: 4, label: "Jadwal", subtitle: "Tanggal, peserta & harga" },
];

const INPUT_CLS =
  "w-full rounded-xl border border-neutral-200 bg-neutral-50 px-4 py-2.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white";

function formatRupiahInput(value: string): string {
  const num = value.replace(/\D/g, "");
  return num.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}

function parseRupiahInput(value: string): string {
  return value.replace(/\./g, "");
}

interface CreateTripFormProps {
  isVerifiedOrganizer: boolean;
}

export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
  const router = useRouter();
  const [step, setStep] = useState<Step>(1);
  const [maxStepReached, setMaxStepReached] = useState<Step>(1);
  const [stepError, setStepError] = useState("");
  const [submitError, setSubmitError] = useState("");
  const [loading, setLoading] = useState(false);

  const [state, setState] = useState<FormState>(INITIAL_STATE);
  const [startDate, setStartDate] = useState<Date | null>(null);
  const [endDate, setEndDate] = useState<Date | null>(null);

  const meta = categoryMeta(state.category);
  const priceNumber = useMemo(
    () => Number(parseRupiahInput(state.priceDisplay) || "0"),
    [state.priceDisplay]
  );
  const isPaidTrip = priceNumber > 0;
  const blockedByVerification = isPaidTrip && !isVerifiedOrganizer;

  function update<K extends keyof FormState>(key: K, value: FormState[K]) {
    setState((prev) => ({ ...prev, [key]: value }));
  }

  function validateStep(target: Step): string | null {
    if (target === 1) {
      if (!state.category) return "Pilih kategori aktivitas dulu";
      return null;
    }
    if (target === 2) {
      if (state.title.trim().length < 3) return "Judul minimal 3 karakter";
      if (state.title.trim().length > LIMITS.MAX_TITLE_LENGTH) {
        return `Judul maksimal ${LIMITS.MAX_TITLE_LENGTH} karakter`;
      }
      if (state.destination.trim().length < 2) {
        return `${meta.destinationLabel} harus diisi`;
      }
      if (state.location.trim().length < 2) return "Lokasi harus diisi";
      return null;
    }
    if (target === 3) {
      // Foto divalidasi saat upload (route + komponen). Di sini cukup cek
      // batas jumlah supaya tidak melampaui kapasitas.
      if (state.imageUrls.length > LIMITS.MAX_IMAGE_URLS) {
        return `Maksimal ${LIMITS.MAX_IMAGE_URLS} foto`;
      }

      for (let d = 0; d < state.itineraryDays.length; d++) {
        const dayItems = state.itineraryDays[d];
        for (const item of dayItems) {
          const hasAnyInput =
            item.startTime.trim() ||
            item.endTime.trim() ||
            item.activity.trim();
          if (!hasAnyInput) continue;
          if (!isValidTimeFormat(item.startTime)) {
            return `Hari ${d + 1}: jam mulai harus HH:mm`;
          }
          if (item.endTime && !isValidTimeFormat(item.endTime)) {
            return `Hari ${d + 1}: jam selesai harus HH:mm`;
          }
          if (
            item.endTime &&
            timeToMinutes(item.endTime) < timeToMinutes(item.startTime)
          ) {
            return `Hari ${d + 1}: jam selesai tidak boleh sebelum jam mulai`;
          }
          if (item.activity.trim().length === 0) {
            return `Hari ${d + 1}: deskripsi aktivitas harus diisi`;
          }
          if (
            item.activity.trim().length > LIMITS.MAX_ITINERARY_ACTIVITY_LENGTH
          ) {
            return `Aktivitas maksimal ${LIMITS.MAX_ITINERARY_ACTIVITY_LENGTH} karakter`;
          }
        }
      }
      const totalItems = flattenItinerary(state.itineraryDays).length;
      if (totalItems > LIMITS.MAX_ITINERARY_ITEMS) {
        return `Maksimal ${LIMITS.MAX_ITINERARY_ITEMS} aktivitas total`;
      }
      return null;
    }
    if (target === 4) {
      if (!startDate) return "Tanggal berangkat harus diisi";
      const max = Number(state.maxParticipants);
      if (!Number.isFinite(max) || max < LIMITS.MIN_PARTICIPANTS) {
        return `Minimal ${LIMITS.MIN_PARTICIPANTS} peserta`;
      }
      if (max > LIMITS.MAX_PARTICIPANTS) {
        return `Maksimal ${LIMITS.MAX_PARTICIPANTS} peserta`;
      }
      if (priceNumber < 0) return "Harga tidak valid";
      if (priceNumber > LIMITS.MAX_PRICE_IDR) {
        return `Harga maksimal Rp ${LIMITS.MAX_PRICE_IDR.toLocaleString("id-ID")}`;
      }
      if (blockedByVerification) {
        return "Trip berbayar butuh verifikasi organizer terlebih dahulu";
      }
      return null;
    }
    return null;
  }

  function goNext() {
    const err = validateStep(step);
    if (err) {
      setStepError(err);
      return;
    }
    setStepError("");
    const next = Math.min(step + 1, STEPS.length) as Step;
    setStep(next);
    if (next > maxStepReached) setMaxStepReached(next);
  }

  function goBack() {
    setStepError("");
    setStep((s) => (Math.max(1, s - 1) as Step));
  }

  function jumpTo(target: Step) {
    if (target > maxStepReached) return;
    setStepError("");
    setStep(target);
  }

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setSubmitError("");

    for (const s of [1, 2, 3, 4] as Step[]) {
      const err = validateStep(s);
      if (err) {
        setStep(s);
        setStepError(err);
        if (s > maxStepReached) setMaxStepReached(s);
        return;
      }
    }

    setLoading(true);

    const fd = new FormData();
    fd.set("category", state.category);
    fd.set("title", state.title.trim());
    fd.set("destination", state.destination.trim());
    fd.set("location", state.location.trim());
    fd.set("description", state.description.trim());
    fd.set("meetingPoint", state.meetingPoint.trim());
    fd.set("whatsIncluded", state.whatsIncluded.trim());
    fd.set("whatsExcluded", state.whatsExcluded.trim());
    fd.set("maxParticipants", state.maxParticipants);
    fd.set("price", parseRupiahInput(state.priceDisplay) || "0");
    if (state.vibe) fd.set("vibe", state.vibe);

    if (startDate) fd.set("date", formatLocalCalendarYmd(startDate));
    if (endDate && startDate) {
      const startYmd = formatLocalCalendarYmd(startDate);
      const endYmd = formatLocalCalendarYmd(endDate);
      if (endYmd !== startYmd) fd.set("endDate", endYmd);
    }

    for (const url of state.imageUrls.map((u) => u.trim()).filter(Boolean)) {
      fd.append("imageUrls", url);
    }

    const itineraryItems = flattenItinerary(state.itineraryDays);
    if (itineraryItems.length > 0) {
      fd.set("itineraryItems", JSON.stringify(itineraryItems));
    }

    const result = await createTripAction(fd);
    setLoading(false);

    if (result.error) {
      setSubmitError(result.error);
      return;
    }
    if (result.tripId) {
      router.push(`/trips/${result.tripId}`);
    }
  }

  const isLastStep = step === STEPS.length;

  return (
    <div className="rounded-2xl border border-neutral-200 bg-white p-4 shadow-sm sm:p-6">
      <Stepper current={step} maxReached={maxStepReached} onJump={jumpTo} />

      <form onSubmit={handleSubmit} className="mt-6">
        {step === 1 && (
          <StepVibe
            category={state.category}
            vibe={state.vibe}
            onCategoryChange={(c) => update("category", c)}
            onVibeChange={(v) => update("vibe", v)}
          />
        )}

        {step === 2 && (
          <StepInfo
            title={state.title}
            destination={state.destination}
            location={state.location}
            description={state.description}
            destinationLabel={meta.destinationLabel}
            destinationPlaceholder={meta.destinationPlaceholder}
            onChange={update}
          />
        )}

        {step === 3 && (
          <StepDetail
            meetingPoint={state.meetingPoint}
            itineraryDays={state.itineraryDays}
            whatsIncluded={state.whatsIncluded}
            whatsExcluded={state.whatsExcluded}
            imageUrls={state.imageUrls}
            onChange={update}
            onError={setStepError}
          />
        )}

        {step === 4 && (
          <StepSchedule
            startDate={startDate}
            endDate={endDate}
            maxParticipants={state.maxParticipants}
            priceDisplay={state.priceDisplay}
            isVerifiedOrganizer={isVerifiedOrganizer}
            blockedByVerification={blockedByVerification}
            onDateChange={(s, e) => {
              setStartDate(s);
              setEndDate(e);
            }}
            onMaxParticipantsChange={(v) => update("maxParticipants", v)}
            onPriceChange={(v) => update("priceDisplay", v)}
            summary={{
              ...state,
              startDate,
              endDate,
              priceNumber,
            }}
          />
        )}

        {(stepError || submitError) && (
          <div className="mt-5 rounded-xl bg-red-50 px-4 py-3 text-sm font-medium text-red-600">
            {stepError || submitError}
          </div>
        )}

        <div className="mt-6 flex items-center justify-between gap-3 border-t border-neutral-100 pt-5">
          <button
            type="button"
            onClick={goBack}
            disabled={step === 1 || loading}
            className="inline-flex items-center gap-1 rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-40"
          >
            <ArrowLeft size={15} strokeWidth={2} aria-hidden />
            Kembali
          </button>

          {isLastStep ? (
            <button
              type="submit"
              disabled={loading || blockedByVerification}
              className="rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700 disabled:cursor-not-allowed disabled:opacity-50"
            >
              {loading
                ? "Membuat Trip..."
                : blockedByVerification
                  ? "Verifikasi diperlukan"
                  : "Buat Trip"}
            </button>
          ) : (
            <button
              type="button"
              onClick={goNext}
              className="inline-flex items-center gap-1 rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700"
            >
              Lanjut
              <ArrowRight size={15} strokeWidth={2} aria-hidden />
            </button>
          )}
        </div>
      </form>
    </div>
  );
}

function Stepper({
  current,
  maxReached,
  onJump,
}: {
  current: Step;
  maxReached: Step;
  onJump: (s: Step) => void;
}) {
  const activeMeta = STEPS[current - 1];

  return (
    <div>
      <ol className="flex items-center">
        {STEPS.map((s, idx) => {
          const isActive = s.id === current;
          const isCompleted = s.id < current;
          const canJump = s.id <= maxReached;
          const isLast = idx === STEPS.length - 1;

          const circleCls = isCompleted
            ? "bg-primary-600 text-white border-primary-600"
            : isActive
              ? "bg-primary-600 text-white border-primary-600 ring-4 ring-primary-100"
              : "bg-white text-neutral-400 border-neutral-200";

          return (
            <li
              key={s.id}
              className={`flex items-center ${isLast ? "" : "flex-1"}`}
            >
              <button
                type="button"
                onClick={() => canJump && onJump(s.id)}
                disabled={!canJump}
                aria-current={isActive ? "step" : undefined}
                aria-label={`Langkah ${s.id}: ${s.label}`}
                className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-full border-2 text-sm font-bold transition-all ${circleCls} ${
                  canJump
                    ? "cursor-pointer hover:scale-105"
                    : "cursor-not-allowed"
                }`}
              >
                {isCompleted ? (
                  <Check size={14} strokeWidth={3} aria-hidden />
                ) : (
                  s.id
                )}
              </button>
              <span
                className={`ml-2 hidden text-xs font-semibold sm:inline ${
                  isActive
                    ? "text-primary-700"
                    : isCompleted
                      ? "text-neutral-700"
                      : "text-neutral-400"
                }`}
              >
                {s.label}
              </span>
              {!isLast && (
                <div
                  className={`mx-2 h-0.5 flex-1 sm:mx-3 ${
                    isCompleted ? "bg-primary-600" : "bg-neutral-200"
                  }`}
                />
              )}
            </li>
          );
        })}
      </ol>

      <div className="mt-4">
        <p className="text-xs font-semibold uppercase tracking-wide text-primary-700">
          Langkah {current} dari {STEPS.length}
        </p>
        <h2 className="mt-0.5 text-lg font-bold text-neutral-900">
          {activeMeta.label}
        </h2>
        <p className="text-xs text-neutral-500">{activeMeta.subtitle}</p>
      </div>
    </div>
  );
}

function StepVibe({
  category,
  vibe,
  onCategoryChange,
  onVibeChange,
}: {
  category: ActivityCategory;
  vibe: Vibe | null;
  onCategoryChange: (c: ActivityCategory) => void;
  onVibeChange: (v: Vibe | null) => void;
}) {
  return (
    <div className="space-y-5">
      <div className="rounded-xl bg-primary-50 p-4">
        <label className="mb-2 block text-sm font-bold text-primary-800">
          Kategori Aktivitas
        </label>
        <div className="flex flex-wrap gap-2">
          {ACTIVITY_CATEGORIES.map((c) => {
            const m = categoryMeta(c);
            const active = c === category;
            return (
              <button
                key={c}
                type="button"
                onClick={() => onCategoryChange(c)}
                aria-pressed={active}
                className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
                  active
                    ? "border-primary-600 bg-primary-600 text-white"
                    : "border-primary-200 bg-white text-primary-800 hover:bg-primary-100"
                }`}
              >
                <span>{m.icon}</span>
                <span>{m.label}</span>
              </button>
            );
          })}
        </div>
      </div>

      <div className="rounded-xl bg-secondary-50 p-4">
        <label className="mb-1 block text-sm font-bold text-secondary-900">
          Vibe Trip{" "}
          <span className="text-xs font-normal text-secondary-700">
            (opsional)
          </span>
        </label>
        <p className="mb-2 text-[11px] text-secondary-700/80">
          Bantu calon peserta menilai apakah ritmenya cocok dengan mereka.
        </p>
        <div className="flex flex-wrap gap-2">
          <button
            type="button"
            onClick={() => onVibeChange(null)}
            aria-pressed={vibe === null}
            className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
              vibe === null
                ? "border-neutral-700 bg-neutral-800 text-white"
                : "border-secondary-200 bg-white text-secondary-800 hover:bg-secondary-100"
            }`}
          >
            Belum diisi
          </button>
          {VIBES.map((v) => {
            const m = vibeMeta(v);
            const active = v === vibe;
            return (
              <button
                key={v}
                type="button"
                onClick={() => onVibeChange(v)}
                aria-pressed={active}
                title={m.description}
                className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
                  active
                    ? "border-secondary-600 bg-secondary-600 text-white"
                    : "border-secondary-200 bg-white text-secondary-800 hover:bg-secondary-100"
                }`}
              >
                <span aria-hidden>{m.icon}</span>
                <span>{m.label}</span>
              </button>
            );
          })}
        </div>
        {vibe && (
          <p className="mt-2 text-[11px] italic text-secondary-700/80">
            {vibeMeta(vibe).description}
          </p>
        )}
      </div>
    </div>
  );
}

function StepInfo({
  title,
  destination,
  location,
  description,
  destinationLabel,
  destinationPlaceholder,
  onChange,
}: {
  title: string;
  destination: string;
  location: string;
  description: string;
  destinationLabel: string;
  destinationPlaceholder: string;
  onChange: <K extends keyof FormState>(key: K, value: FormState[K]) => void;
}) {
  return (
    <div className="space-y-5">
      <div>
        <label
          htmlFor="title"
          className="mb-1.5 block text-sm font-semibold text-neutral-700"
        >
          Judul Trip
        </label>
        <input
          id="title"
          type="text"
          value={title}
          onChange={(e) => onChange("title", e.target.value)}
          maxLength={LIMITS.MAX_TITLE_LENGTH}
          required
          className={INPUT_CLS}
          placeholder="contoh: Open Trip Papandayan Weekend"
        />
      </div>

      <div className="grid gap-4 sm:grid-cols-2">
        <div>
          <label
            htmlFor="destination"
            className="mb-1.5 block text-sm font-semibold text-neutral-700"
          >
            {destinationLabel}
          </label>
          <input
            id="destination"
            type="text"
            value={destination}
            onChange={(e) => onChange("destination", e.target.value)}
            maxLength={LIMITS.MAX_DESTINATION_LENGTH}
            required
            className={INPUT_CLS}
            placeholder={destinationPlaceholder}
          />
        </div>
        <div>
          <label
            htmlFor="location"
            className="mb-1.5 block text-sm font-semibold text-neutral-700"
          >
            Lokasi
          </label>
          <input
            id="location"
            type="text"
            value={location}
            onChange={(e) => onChange("location", e.target.value)}
            maxLength={LIMITS.MAX_LOCATION_LENGTH}
            required
            className={INPUT_CLS}
            placeholder="Garut, Jawa Barat"
          />
        </div>
      </div>

      <div>
        <label
          htmlFor="description"
          className="mb-1.5 block text-sm font-semibold text-neutral-700"
        >
          Deskripsi{" "}
          <span className="text-xs font-normal text-neutral-400">
            (opsional)
          </span>
        </label>
        <textarea
          id="description"
          rows={4}
          value={description}
          onChange={(e) => onChange("description", e.target.value)}
          maxLength={LIMITS.MAX_DESCRIPTION_LENGTH}
          className={INPUT_CLS}
          placeholder="Ringkasan trip, vibe, level kesulitan..."
        />
      </div>
    </div>
  );
}

function StepDetail({
  meetingPoint,
  itineraryDays,
  whatsIncluded,
  whatsExcluded,
  imageUrls,
  onChange,
  onError,
}: {
  meetingPoint: string;
  itineraryDays: ItineraryDays;
  whatsIncluded: string;
  whatsExcluded: string;
  imageUrls: string[];
  onChange: <K extends keyof FormState>(key: K, value: FormState[K]) => void;
  onError: (msg: string) => void;
}) {
  return (
    <div className="space-y-5">
      <div>
        <label
          htmlFor="meetingPoint"
          className="mb-1.5 block text-sm font-semibold text-neutral-700"
        >
          Meeting point{" "}
          <span className="text-xs font-normal text-neutral-400">
            (opsional)
          </span>
        </label>
        <input
          id="meetingPoint"
          type="text"
          value={meetingPoint}
          onChange={(e) => onChange("meetingPoint", e.target.value)}
          maxLength={LIMITS.MAX_MEETING_POINT_LENGTH}
          className={INPUT_CLS}
          placeholder="contoh: Alfamart Cicaheum, 05:00 WIB"
        />
      </div>

      <ItineraryBuilder
        days={itineraryDays}
        onChange={(next) => onChange("itineraryDays", next)}
      />

      <div className="grid gap-4 sm:grid-cols-2">
        <div>
          <label
            htmlFor="whatsIncluded"
            className="mb-1.5 block text-sm font-semibold text-neutral-700"
          >
            Termasuk{" "}
            <span className="text-xs font-normal text-neutral-400">
              (opsional)
            </span>
          </label>
          <textarea
            id="whatsIncluded"
            rows={4}
            value={whatsIncluded}
            onChange={(e) => onChange("whatsIncluded", e.target.value)}
            maxLength={LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH}
            className={INPUT_CLS}
            placeholder="Transport, konsumsi, tenda, …"
          />
        </div>
        <div>
          <label
            htmlFor="whatsExcluded"
            className="mb-1.5 block text-sm font-semibold text-neutral-700"
          >
            Tidak termasuk{" "}
            <span className="text-xs font-normal text-neutral-400">
              (opsional)
            </span>
          </label>
          <textarea
            id="whatsExcluded"
            rows={4}
            value={whatsExcluded}
            onChange={(e) => onChange("whatsExcluded", e.target.value)}
            maxLength={LIMITS.MAX_TRIP_BULLET_SECTION_LENGTH}
            className={INPUT_CLS}
            placeholder="Tiket masuk TN, sleeping bag, …"
          />
        </div>
      </div>

      <TripImageUpload
        value={imageUrls}
        onChange={(urls) => onChange("imageUrls", urls)}
        onError={onError}
      />
    </div>
  );
}

function ItineraryBuilder({
  days,
  onChange,
}: {
  days: ItineraryDays;
  onChange: (next: ItineraryDays) => void;
}) {
  const totalItems = days.reduce((sum, d) => sum + d.length, 0);
  const canAddDay = days.length < LIMITS.MAX_ITINERARY_DAYS;
  const canAddItem = totalItems < LIMITS.MAX_ITINERARY_ITEMS;

  function addDay() {
    if (!canAddDay) return;
    onChange([...days, [{ startTime: "", endTime: "", activity: "" }]]);
  }

  function removeDay(dayIdx: number) {
    onChange(days.filter((_, i) => i !== dayIdx));
  }

  function addItem(dayIdx: number) {
    if (!canAddItem) return;
    const next = days.map((d, i) =>
      i === dayIdx ? [...d, { startTime: "", endTime: "", activity: "" }] : d
    );
    onChange(next);
  }

  function removeItem(dayIdx: number, itemIdx: number) {
    const next = days.map((d, i) =>
      i === dayIdx ? d.filter((_, j) => j !== itemIdx) : d
    );
    onChange(next);
  }

  function updateItem(
    dayIdx: number,
    itemIdx: number,
    field: keyof DraftItineraryItem,
    value: string
  ) {
    const next = days.map((d, i) =>
      i === dayIdx
        ? d.map((it, j) => (j === itemIdx ? { ...it, [field]: value } : it))
        : d
    );
    onChange(next);
  }

  return (
    <div>
      <div className="mb-1.5 flex items-baseline justify-between gap-3">
        <label className="block text-sm font-semibold text-neutral-700">
          Itinerary per hari{" "}
          <span className="text-xs font-normal text-neutral-400">
            (opsional)
          </span>
        </label>
        <span className="text-xs text-neutral-400">
          {totalItems}/{LIMITS.MAX_ITINERARY_ITEMS}
        </span>
      </div>
      <p className="mb-3 text-[11px] text-neutral-500">
        Susun aktivitas per hari + jamnya supaya peserta tahu alur trip.
        Itinerary jelas bikin trust naik drastis.
      </p>

      {days.length === 0 ? (
        <div className="rounded-xl border border-dashed border-neutral-300 bg-neutral-50/60 px-4 py-6 text-center">
          <p className="mb-3 text-xs text-neutral-500">
            Belum ada itinerary. Tambahkan hari pertama untuk mulai.
          </p>
          <button
            type="button"
            onClick={addDay}
            className="rounded-xl bg-primary-600 px-4 py-2 text-xs font-semibold text-white shadow-sm hover:bg-primary-700"
          >
            + Tambah Hari 1
          </button>
        </div>
      ) : (
        <div className="space-y-4">
          {days.map((items, dayIdx) => (
            <div
              key={dayIdx}
              className="rounded-xl border border-neutral-200 bg-white p-3 sm:p-4"
            >
              <div className="mb-3 flex items-center justify-between gap-2">
                <h4 className="text-sm font-bold text-primary-800">
                  Hari {dayIdx + 1}
                </h4>
                <button
                  type="button"
                  onClick={() => removeDay(dayIdx)}
                  className="rounded-lg px-2 py-1 text-[11px] font-medium text-red-500 hover:bg-red-50"
                >
                  Hapus hari
                </button>
              </div>

              {items.length === 0 ? (
                <p className="mb-2 text-[11px] text-neutral-400">
                  Belum ada aktivitas di hari ini.
                </p>
              ) : (
                <ul className="space-y-2">
                  {items.map((item, itemIdx) => (
                    <li
                      key={itemIdx}
                      className="rounded-lg border border-neutral-100 bg-neutral-50/60 p-2.5"
                    >
                      <div className="flex flex-col gap-2 sm:flex-row sm:items-start">
                        <div className="flex shrink-0 gap-2">
                          <div className="w-32">
                            <label
                              htmlFor={`itin-${dayIdx}-${itemIdx}-start`}
                              className="mb-0.5 block text-[10px] font-semibold uppercase tracking-wide text-neutral-500"
                            >
                              Mulai
                            </label>
                            <TimeField
                              id={`itin-${dayIdx}-${itemIdx}-start`}
                              value={item.startTime}
                              onChange={(v) =>
                                updateItem(dayIdx, itemIdx, "startTime", v)
                              }
                            />
                          </div>
                          <div className="w-32">
                            <label
                              htmlFor={`itin-${dayIdx}-${itemIdx}-end`}
                              className="mb-0.5 block text-[10px] font-semibold uppercase tracking-wide text-neutral-500"
                            >
                              Selesai
                            </label>
                            <TimeField
                              id={`itin-${dayIdx}-${itemIdx}-end`}
                              value={item.endTime}
                              onChange={(v) =>
                                updateItem(dayIdx, itemIdx, "endTime", v)
                              }
                              clearable
                            />
                          </div>
                        </div>
                        <div className="min-w-0 flex-1">
                          <label className="mb-0.5 block text-[10px] font-semibold uppercase tracking-wide text-neutral-500">
                            Aktivitas
                          </label>
                          <input
                            type="text"
                            value={item.activity}
                            onChange={(e) =>
                              updateItem(
                                dayIdx,
                                itemIdx,
                                "activity",
                                e.target.value
                              )
                            }
                            maxLength={LIMITS.MAX_ITINERARY_ACTIVITY_LENGTH}
                            placeholder="contoh: Trekking ke Pondok Salada"
                            className="w-full rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-800 placeholder:text-neutral-400 focus:border-primary-400"
                          />
                        </div>
                        <button
                          type="button"
                          onClick={() => removeItem(dayIdx, itemIdx)}
                          aria-label="Hapus aktivitas"
                          className="self-end rounded-lg px-2 py-1.5 text-neutral-400 hover:bg-red-50 hover:text-red-500 sm:self-center"
                        >
                          <X size={16} strokeWidth={2} aria-hidden />
                        </button>
                      </div>
                    </li>
                  ))}
                </ul>
              )}

              <button
                type="button"
                onClick={() => addItem(dayIdx)}
                disabled={!canAddItem}
                className="mt-3 rounded-lg border border-dashed border-primary-300 bg-primary-50/60 px-3 py-1.5 text-xs font-semibold text-primary-700 hover:bg-primary-100 disabled:cursor-not-allowed disabled:opacity-50"
              >
                + Tambah aktivitas
              </button>
            </div>
          ))}

          {canAddDay && (
            <button
              type="button"
              onClick={addDay}
              className="w-full rounded-xl border border-dashed border-neutral-300 bg-neutral-50/60 py-3 text-xs font-semibold text-neutral-600 hover:border-primary-400 hover:text-primary-700"
            >
              + Tambah Hari {days.length + 1}
            </button>
          )}
        </div>
      )}
    </div>
  );
}

function StepSchedule({
  startDate,
  endDate,
  maxParticipants,
  priceDisplay,
  isVerifiedOrganizer,
  blockedByVerification,
  onDateChange,
  onMaxParticipantsChange,
  onPriceChange,
  summary,
}: {
  startDate: Date | null;
  endDate: Date | null;
  maxParticipants: string;
  priceDisplay: string;
  isVerifiedOrganizer: boolean;
  blockedByVerification: boolean;
  onDateChange: (start: Date | null, end: Date | null) => void;
  onMaxParticipantsChange: (v: string) => void;
  onPriceChange: (v: string) => void;
  summary: FormState & {
    startDate: Date | null;
    endDate: Date | null;
    priceNumber: number;
  };
}) {
  function handlePriceChange(e: React.ChangeEvent<HTMLInputElement>) {
    const raw = e.target.value.replace(/\D/g, "");
    onPriceChange(raw ? formatRupiahInput(raw) : "");
  }

  return (
    <div className="space-y-5">
      <div className="grid gap-4 sm:grid-cols-2">
        <div>
          <label className="mb-1.5 block text-sm font-semibold text-neutral-700">
            Tanggal berangkat — pulang
          </label>
          <DateRangeField
            startDate={startDate}
            endDate={endDate}
            onChange={(s, e) => onDateChange(s, e)}
            minDate={new Date()}
          />
        </div>

        <div>
          <label
            htmlFor="maxParticipants"
            className="mb-1.5 block text-sm font-semibold text-neutral-700"
          >
            Maks Peserta
          </label>
          <div className="relative">
            <span className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
              <Users size={16} strokeWidth={1.75} aria-hidden />
            </span>
            <input
              id="maxParticipants"
              type="number"
              required
              min={LIMITS.MIN_PARTICIPANTS}
              max={LIMITS.MAX_PARTICIPANTS}
              value={maxParticipants}
              onChange={(e) => onMaxParticipantsChange(e.target.value)}
              className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-9 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
              placeholder="10"
            />
          </div>
        </div>
      </div>

      <div>
        <label
          htmlFor="priceDisplay"
          className="mb-1.5 block text-sm font-semibold text-neutral-700"
        >
          Harga per Orang
          {!isVerifiedOrganizer && (
            <span className="ml-2 text-xs font-normal text-neutral-500">
              (isi 0 untuk trip gratis)
            </span>
          )}
        </label>
        <div className="relative">
          <span className="absolute left-3 top-1/2 -translate-y-1/2 text-sm font-semibold text-neutral-500">
            Rp
          </span>
          <input
            id="priceDisplay"
            type="text"
            inputMode="numeric"
            required
            value={priceDisplay}
            onChange={handlePriceChange}
            className="w-full rounded-xl border border-neutral-200 bg-neutral-50 py-2.5 pl-10 pr-4 text-sm text-neutral-800 placeholder:text-neutral-400 focus:bg-white"
            placeholder="150.000"
          />
        </div>
        {blockedByVerification && (
          <p className="mt-2 flex items-center gap-1.5 text-xs font-medium text-amber-700">
            <CircleAlert size={14} strokeWidth={2} aria-hidden />
            Trip berbayar butuh verifikasi organizer terlebih dahulu.
          </p>
        )}
      </div>

      <ReviewSummary summary={summary} />
    </div>
  );
}

function ReviewSummary({
  summary,
}: {
  summary: FormState & {
    startDate: Date | null;
    endDate: Date | null;
    priceNumber: number;
  };
}) {
  const catMeta = categoryMeta(summary.category);
  const vibeM = summary.vibe ? vibeMeta(summary.vibe) : null;
  const imageCount = summary.imageUrls.filter((u) => u.trim()).length;
  const itineraryItemCount = flattenItinerary(summary.itineraryDays).length;
  const itineraryDayCount = summary.itineraryDays.filter(
    (d) => d.some((it) => it.activity.trim().length > 0)
  ).length;
  const priceLabel =
    summary.priceNumber > 0
      ? `Rp ${summary.priceNumber.toLocaleString("id-ID")}`
      : "Gratis";

  const dateLabel = (() => {
    if (!summary.startDate) return "Belum dipilih";
    const opts: Intl.DateTimeFormatOptions = {
      day: "numeric",
      month: "short",
      year: "numeric",
    };
    const start = summary.startDate.toLocaleDateString("id-ID", opts);
    if (
      summary.endDate &&
      formatLocalCalendarYmd(summary.endDate) !==
        formatLocalCalendarYmd(summary.startDate)
    ) {
      const end = summary.endDate.toLocaleDateString("id-ID", opts);
      return `${start} — ${end}`;
    }
    return start;
  })();

  return (
    <div className="rounded-2xl border border-neutral-200 bg-neutral-50 p-4">
      <p className="mb-3 text-xs font-bold uppercase tracking-wide text-neutral-500">
        Ringkasan
      </p>
      <dl className="grid grid-cols-1 gap-x-4 gap-y-2 text-sm sm:grid-cols-2">
        <SummaryRow
          label="Kategori"
          value={`${catMeta.icon} ${catMeta.label}`}
        />
        <SummaryRow
          label="Vibe"
          value={vibeM ? `${vibeM.icon} ${vibeM.label}` : "—"}
        />
        <SummaryRow
          label="Judul"
          value={summary.title.trim() || "—"}
          full
        />
        <SummaryRow
          label={catMeta.destinationLabel}
          value={summary.destination.trim() || "—"}
        />
        <SummaryRow label="Lokasi" value={summary.location.trim() || "—"} />
        <SummaryRow label="Tanggal" value={dateLabel} />
        <SummaryRow
          label="Maks peserta"
          value={summary.maxParticipants || "—"}
        />
        <SummaryRow label="Harga / orang" value={priceLabel} />
        <SummaryRow
          label="Foto"
          value={imageCount > 0 ? `${imageCount} foto` : "Belum ada"}
        />
        <SummaryRow
          label="Itinerary"
          value={
            itineraryItemCount > 0
              ? `${itineraryDayCount} hari · ${itineraryItemCount} aktivitas`
              : "Belum ada"
          }
        />
      </dl>
    </div>
  );
}

function SummaryRow({
  label,
  value,
  full = false,
}: {
  label: string;
  value: string;
  full?: boolean;
}) {
  return (
    <div className={full ? "sm:col-span-2" : undefined}>
      <dt className="text-[11px] font-medium uppercase tracking-wide text-neutral-500">
        {label}
      </dt>
      <dd className="truncate font-semibold text-neutral-800">{value}</dd>
    </div>
  );
}
