"use client";

import { Button } from "@/components/ui/button";
import { type ServiceItem } from "@/core/api/services/other-services";
import { type TagItem } from "@/core/api/services/tags";
import { getFixPackageAction } from "@/features/packages/actions/fix-packages-actions";
import {
  createTagAction,
  getTagsAction,
} from "@/features/packages/actions/tags-actions";
import { useToastEnhanced } from "@/hooks/use-toast-enhanced";
import { getStorageUrl } from "@/lib/utils";
import { FIX_PACKAGES_ADMIN } from "@/lib/strings";
import {
  fixPackageSchema,
  type FixPackageInput,
  type PackageHotelInput,
  type PackageImageInput,
  type PackageItineraryInput,
  type PackageVehicleInput,
} from "@/lib/validations/fix-packages";
import type {
  CityOption,
  DayEntry,
  ExtrasEntry,
  SightseeingItem,
} from "@/features/packages/types/package-form.types";
import { HotelDialog, ImageDialog, VehicleDialog } from "./dialogs";
import {
  AddTagModal,
  BannerSliderCard,
  createValidateImageSize,
  ExtrasCard,
  ItineraryDetailsCard,
  OtherServicesCard,
  PackageInfoCard,
  SightseeingModal,
  TagsCard,
} from "./form-sections";
import type { ApiPackage } from "./lib";
import { mapApiPackageToFormState, submitFixPackage } from "./lib";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowLeft } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form";

const packageFetchCache = new Map<string, Promise<any>>();
function fetchPackageOnce(id: string) {
  if (!packageFetchCache.has(id)) {
    const promise = getFixPackageAction(id).finally(() =>
      packageFetchCache.delete(id)
    );
    packageFetchCache.set(id, promise);
  }
  return packageFetchCache.get(id)!;
}

interface PackageFormClientProps {
  mode: "add" | "edit";
  packageId?: string;
  initialTags?: TagItem[];
  initialServices?: ServiceItem[];
  initialCities?: CityOption[];
}

export function PackageFormClient({
  mode,
  packageId,
  initialTags = [],
  initialServices = [],
  initialCities = [],
}: PackageFormClientProps) {
  const router = useRouter();
  const toast = useToastEnhanced();
  const [isSubmitting, setIsSubmitting] = useState(false);
  // vehicles/hotels are now managed per day — no global state needed
  const [vehicleTargetDayId, setVehicleTargetDayId] = useState<number | null>(
    null
  );
  const [hotelTargetDayId, setHotelTargetDayId] = useState<number | null>(null);
  const [extraImages, setExtraImages] = useState<PackageImageInput[]>([]);

  const [isVehicleDialogOpen, setIsVehicleDialogOpen] = useState(false);
  const [isHotelDialogOpen, setIsHotelDialogOpen] = useState(false);
  const [isImageDialogOpen, setIsImageDialogOpen] = useState(false);

  // Tags state – seeded from server-fetched props
  const [availableTags, setAvailableTags] = useState<TagItem[]>(initialTags);
  const [tagsLoading, setTagsLoading] = useState(false);
  const [selectedTagIds, setSelectedTagIds] = useState<number[]>([]); // stores numeric IDs
  const [isAddTagModalOpen, setIsAddTagModalOpen] = useState(false);
  const [newTagName, setNewTagName] = useState("");
  const [isTagSaving, setIsTagSaving] = useState(false);

  // Other Services state – seeded from server-fetched props
  const [availableServices, _setAvailableServices] =
    useState<ServiceItem[]>(initialServices);
  const [servicesLoading, _setServicesLoading] = useState(false);

  // Cities state – seeded from server-fetched props
  const [_availableCities, _setAvailableCities] =
    useState<CityOption[]>(initialCities);
  const [citiesLoading, _setCitiesLoading] = useState(false);

  // Extras state
  const [extrasEntries, setExtrasEntries] = useState<ExtrasEntry[]>([
    { id: Date.now(), title: "", description: "" },
  ]);
  const updateExtras = (id: number, patch: Partial<ExtrasEntry>) =>
    setExtrasEntries((prev) =>
      prev.map((e) => (e.id === id ? { ...e, ...patch } : e))
    );
  const removeExtras = (id: number) =>
    setExtrasEntries((prev) => prev.filter((e) => e.id !== id));

  const [editingVehicleIndex, setEditingVehicleIndex] = useState<number | null>(
    null
  );
  const [editingHotelIndex, setEditingHotelIndex] = useState<number | null>(
    null
  );
  const [editingImageIndex, setEditingImageIndex] = useState<number | null>(
    null
  );

  const [bannerPreview, setBannerPreview] = useState<string | null>(null);

  const makeDefaultDay = (dayNum: number): DayEntry => ({
    id: Date.now() + dayNum,
    isDayEntry: true,
    includeNight: false,
    day: dayNum,
    day_title: "",
    day_city: "",
    city_id: null,
    staying_location: "",
    meal_breakfast: false,
    meal_lunch: false,
    meal_dinner: false,
    sightseeings: [],
    description: "",
    vehicles: [],
    hotels: [],
  });

  const [dayEntries, setDayEntries] = useState<DayEntry[]>([makeDefaultDay(1)]);
  const [collapsedDayIds, setCollapsedDayIds] = useState<Set<number>>(
    new Set()
  );
  const updateDay = (id: number, patch: Partial<DayEntry>) =>
    setDayEntries((prev) =>
      prev.map((d) => (d.id === id ? { ...d, ...patch } : d))
    );

  // Sightseeing modal state (shared, driven by target day id)
  const [isSightseeingModalOpen, setIsSightseeingModalOpen] = useState(false);
  const [sightseeingTargetId, setSightseeingTargetId] = useState<number | null>(
    null
  );
  const [sightseeingDraft, setSightseeingDraft] = useState<SightseeingItem[]>(
    []
  );

  const {
    register,
    handleSubmit,
    setValue,
    setError,
    watch,
    formState: { errors, isSubmitted },
  } = useForm<FixPackageInput>({
    resolver: zodResolver(fixPackageSchema) as any,
    defaultValues: {
      name: "",
      location_detail: "",
      other_detail: "",
      sort: null,
      price: 0,
      discount_price: 0,
      total_days: 1,
      total_nights: 0,
      status: "active",
      package_type: "normal",
      package_category: "fix_package",
      add_more_images: false,
      start_date: "",
      end_date: "",
      vehicles: [],
      hotels: [],
      images: [],
      tag_ids: [],
      service_ids: [],
      itineraries: [],
      extras: [],
    },
  });

  const _watchStatus = watch("status");
  const [isDataLoaded, setIsDataLoaded] = useState(mode === "add");
  const [originalPackageData, setOriginalPackageData] = useState<any>(null);

  const quillRef = useRef<HTMLDivElement | null>(null);
  const extrasRefs = useRef<(HTMLDivElement | null)[]>([]);
  const itineraryRefs = useRef<(HTMLDivElement | null)[]>([]);
  const _watchPackageType = watch("package_type");

  // Load existing package data in edit mode
  useEffect(() => {
    let cancelled = false;

    async function fetchPackage() {
      if (mode !== "edit" || !packageId) return;
      setIsSubmitting(true);
      try {
        const result = await fetchPackageOnce(packageId);
        if (cancelled) return;
        if (result.success && result.data) {
          const update = mapApiPackageToFormState(
            result.data as ApiPackage,
            initialTags,
            initialServices,
            getStorageUrl
          );
          if (cancelled) return;
          Object.entries(update.formValues).forEach(([key, value]) => {
            setValue(key as keyof FixPackageInput, value as never);
          });
          setBannerPreview(update.bannerPreview);
          setSelectedTagIds(update.selectedTagIds);
          setExtrasEntries(update.extrasEntries);
          setDayEntries(update.dayEntries);
          setExtraImages(update.extraImages);
          setOriginalPackageData(update.originalPackageData);
        }
      } catch {
        if (cancelled) return;
        toast.error({
          title: "Error",
          description: FIX_PACKAGES_ADMIN.FAILED_TO_LOAD_PACKAGE_DETAILS,
        });
      } finally {
        if (!cancelled) {
          setIsSubmitting(false);
          setIsDataLoaded(true);
        }
      }
    }
    fetchPackage();

    return () => {
      cancelled = true;
    };
  }, [mode, packageId, setValue, toast, initialTags, initialServices]);

  // Fetch tags — called after tag creation to refresh the list
  const fetchTags = async () => {
    setTagsLoading(true);
    try {
      const result = await getTagsAction();
      if (result.success && result.data) {
        setAvailableTags(result.data);
      }
    } finally {
      setTagsLoading(false);
    }
  };

  // Keep total_days and total_nights in sync with itinerary entries
  useEffect(() => {
    setValue("total_days", dayEntries.filter((d) => d.isDayEntry).length);
    setValue(
      "total_nights" as any,
      dayEntries.filter((d) => d.includeNight).length
    );
  }, [dayEntries, setValue]);

  // Sync per-day vehicles/hotels to form-level arrays (flatten all days)
  useEffect(() => {
    const allVehicles = dayEntries.flatMap((d) => d.vehicles);
    setValue("vehicles", allVehicles);
  }, [dayEntries, setValue]);

  useEffect(() => {
    const allHotels = dayEntries.flatMap((d) => d.hotels);
    setValue("hotels", allHotels);
  }, [dayEntries, setValue]);

  // Update form value when images change
  useEffect(() => {
    setValue("images", extraImages);
  }, [extraImages, setValue]);

  // Sync extras → form
  useEffect(() => {
    setValue(
      "extras",
      extrasEntries.map(({ title, description }) => ({ title, description }))
    );
  }, [extrasEntries, setValue]);

  // Sync dayEntries → itineraries form field
  useEffect(() => {
    const itineraries: PackageItineraryInput[] = dayEntries.map((d) => ({
      day: d.day,
      title: d.day_title,
      city_id: d.city_id ?? (undefined as unknown as number),
      night_stay_location: d.staying_location || undefined,
      is_day_plan: d.isDayEntry ? "1" : "0",
      is_night_plan: d.includeNight ? "1" : "0",
      description: d.description || undefined,
      is_breakfast: d.meal_breakfast ? "1" : "0",
      is_lunch: d.meal_lunch ? "1" : "0",
      is_dinner: d.meal_dinner ? "1" : "0",
      status: "active",
      sight_seeing_ids: d.sightseeings.map((s) => s.id),
      vehicles: d.vehicles,
      hotels: d.hotels,
    }));
    setValue("itineraries", itineraries, { shouldValidate: isSubmitted });
  }, [dayEntries, setValue, isSubmitted]);

  const onSubmit = async (data: FixPackageInput) => {
    setIsSubmitting(true);
    try {
      const extrasWithHtml = extrasEntries.map((extra, index) => ({
        ...extra,
        description:
          extrasRefs.current?.[index]?.querySelector(".ql-editor")?.innerHTML ??
          extra.description,
      }));

      // Update itinerary descriptions inside dayEntries
      const updatedDayEntries = dayEntries.map((day, index) => ({
        ...day,
        description:
          itineraryRefs.current?.[index]?.querySelector(".ql-editor")
            ?.innerHTML ?? day.description,
      }));

      // Merge extras into form data
      const updatedData = {
        ...data,
        extras: extrasWithHtml,
      };

      // Submit
      await submitFixPackage({
        mode,
        data: updatedData,
        dayEntries: updatedDayEntries,
        originalPackageData,
        getEditorHtml: () =>
          quillRef.current?.querySelector(".ql-editor")?.innerHTML ?? "",
        packageId: packageId ?? undefined,
      });

      toast.success({
        title:
          mode === "add"
            ? FIX_PACKAGES_ADMIN.PACKAGE_ADDED
            : FIX_PACKAGES_ADMIN.PACKAGE_UPDATED,
        description: `${data.name} has been ${
          mode === "add"
            ? FIX_PACKAGES_ADMIN.ADDED_SUCCESS
            : FIX_PACKAGES_ADMIN.UPDATED_SUCCESS
        } ${FIX_PACKAGES_ADMIN.SUCCESS_SUFFIX}`,
      });
      router.push("/admin/fix-packages");
    } catch (err: unknown) {
      const errPayload = err as {
        message?: string;
        errors?: Record<string, string[]>;
        response?: {
          data?: { message?: string; errors?: Record<string, string[]> };
        };
      };
      const apiMsg =
        errPayload?.response?.data?.message ??
        errPayload?.message ??
        (err as Error)?.message ??
        FIX_PACKAGES_ADMIN.ERROR_SAVING_PACKAGE;

      // Use validation errors from server (thrown by submitFixPackage) or from axios response
      const fieldErrors =
        errPayload?.errors ?? errPayload?.response?.data?.errors;

      /** Normalize API field path to react-hook-form path (e.g. itineraries[0][title] → itineraries.0.title) */
      const toFormPath = (path: string) =>
        path.replace(/\]?\[/g, ".").replace(/\]/g, "");

      /** Use friendly message for itinerary title field */
      const toDisplayMessage = (fieldPath: string, msg: string) =>
        /^itineraries\.\d+\.title$/.test(fieldPath)
          ? "Itinerary title required"
          : msg;

      let highlighted = false;

      if (fieldErrors && typeof fieldErrors === "object") {
        const entries = Object.entries(fieldErrors);
        entries.forEach(([rawPath, messages], index) => {
          const msg = Array.isArray(messages) ? messages[0] : messages;
          if (rawPath && msg) {
            const fieldPath = toFormPath(rawPath);
            setError(
              fieldPath as keyof FixPackageInput,
              { type: "server", message: toDisplayMessage(fieldPath, msg) },
              index === 0 ? { shouldFocus: true } : undefined
            );
            highlighted = true;
            const itineraryMatch = /^itineraries\.(\d+)/.exec(fieldPath);
            if (itineraryMatch && dayEntries[Number(itineraryMatch[1])]) {
              setCollapsedDayIds((prev) => {
                const next = new Set(prev);
                next.delete(dayEntries[Number(itineraryMatch[1])].id);
                return next;
              });
            }
          }
        });
      } else if (typeof apiMsg === "string") {
        const fieldMatch =
          apiMsg.match(/The\s+([\w.[\]]+)\s+(?:field\s+)?(?:is\s+)?([^.]+)/i) ??
          apiMsg.match(/([\w.]+)\s+(?:is\s+)?(?:required|invalid)/i);
        if (fieldMatch) {
          const fieldPath = toFormPath(fieldMatch[1].trim());
          const rawMessage = (
            fieldMatch[2]?.trim() ?? "This field is required"
          ).replace(/\.$/, "");
          setError(
            fieldPath as keyof FixPackageInput,
            {
              type: "server",
              message: toDisplayMessage(fieldPath, rawMessage),
            },
            { shouldFocus: true }
          );
          highlighted = true;
          const itineraryMatch = /^itineraries\.(\d+)/.exec(fieldPath);
          if (itineraryMatch && dayEntries[Number(itineraryMatch[1])]) {
            setCollapsedDayIds((prev) => {
              const next = new Set(prev);
              next.delete(dayEntries[Number(itineraryMatch[1])].id);
              return next;
            });
          }
        }
      }

      if (highlighted) {
        toast.error({
          title: FIX_PACKAGES_ADMIN.FAILED_TO_SAVE_PACKAGE,
          description: "Please fix the highlighted field(s).",
        });
      } else {
        toast.error({
          title: FIX_PACKAGES_ADMIN.FAILED_TO_SAVE_PACKAGE,
          description: apiMsg,
        });
      }
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleAddVehicle = (vehicle: PackageVehicleInput) => {
    if (vehicleTargetDayId === null) return;
    updateDay(vehicleTargetDayId, {
      vehicles: [
        ...(dayEntries.find((d) => d.id === vehicleTargetDayId)?.vehicles ??
          []),
        vehicle,
      ],
    });
    setIsVehicleDialogOpen(false);
  };

  const handleEditVehicle = (vehicle: PackageVehicleInput) => {
    if (vehicleTargetDayId === null || editingVehicleIndex === null) return;
    const day = dayEntries.find((d) => d.id === vehicleTargetDayId);
    if (!day) return;
    const updated = [...day.vehicles];
    updated[editingVehicleIndex] = vehicle;
    updateDay(vehicleTargetDayId, { vehicles: updated });
    setEditingVehicleIndex(null);
    setIsVehicleDialogOpen(false);
  };

  const handleAddHotel = (hotel: PackageHotelInput) => {
    if (hotelTargetDayId === null) return;
    updateDay(hotelTargetDayId, {
      hotels: [
        ...(dayEntries.find((d) => d.id === hotelTargetDayId)?.hotels ?? []),
        hotel,
      ],
    });
    setIsHotelDialogOpen(false);
  };

  const handleEditHotel = (hotel: PackageHotelInput) => {
    if (hotelTargetDayId === null || editingHotelIndex === null) return;
    const day = dayEntries.find((d) => d.id === hotelTargetDayId);
    if (!day) return;
    const updated = [...day.hotels];
    updated[editingHotelIndex] = hotel;
    updateDay(hotelTargetDayId, { hotels: updated });
    setEditingHotelIndex(null);
    setIsHotelDialogOpen(false);
  };

  const handleAddImage = (img: PackageImageInput) => {
    const updated = [...extraImages, img];
    setExtraImages(updated);
    setValue("images", updated);
    setIsImageDialogOpen(false);
  };

  const handleEditImage = (img: PackageImageInput) => {
    if (editingImageIndex === null) return;
    // Map visible index to actual index in the full array (includes deleted)
    const visibleImages = extraImages.filter((im) => im.status !== 0);
    const target = visibleImages[editingImageIndex];
    if (!target) return;
    const actualIndex = extraImages.indexOf(target);
    if (actualIndex === -1) return;
    const updated = [...extraImages];
    // Preserve id and status from the existing entry
    updated[actualIndex] = {
      ...img,
      id: extraImages[actualIndex].id,
      status: extraImages[actualIndex].status,
    };
    setExtraImages(updated);
    setValue("images", updated);
    setEditingImageIndex(null);
    setIsImageDialogOpen(false);
  };

  const handleDeleteImage = (index: number) => {
    // For visible images, find the actual index in the full array (which includes deleted ones)
    const visibleImages = extraImages.filter((img) => img.status !== 0);
    const target = visibleImages[index];
    if (!target) return;
    const actualIndex = extraImages.indexOf(target);
    if (actualIndex === -1) return;

    let updated: PackageImageInput[];
    if (target.id) {
      // Existing image from API — mark as deleted (status: 0) instead of removing
      updated = extraImages.map((img, i) =>
        i === actualIndex ? { ...img, status: 0 as const } : img
      );
    } else {
      // Newly added image (no id) — just remove from array
      updated = extraImages.filter((_, i) => i !== actualIndex);
    }
    setExtraImages(updated);
    setValue("images", updated);
  };

  const validateImageSize = useMemo(
    () => createValidateImageSize(toast),
    [toast]
  );

  const handleBannerChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      if (!validateImageSize(file)) {
        e.target.value = "";
        return;
      }
      // Revoke old preview URL to avoid memory leaks
      if (bannerPreview && bannerPreview.startsWith("blob:")) {
        URL.revokeObjectURL(bannerPreview);
      }
      setBannerPreview(URL.createObjectURL(file));
      setValue("banner_image", file);
    }
  };

  const handleSliderImagesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files ?? []);
    if (!files.length) return;
    const MAX = 6;
    const activeCount = extraImages.filter((img) => img.status !== 0).length;
    const remaining = MAX - activeCount;
    if (remaining <= 0) {
      toast.error({
        title: FIX_PACKAGES_ADMIN.LIMIT_REACHED,
        description: FIX_PACKAGES_ADMIN.MAX_SLIDER_IMAGES,
      });
      e.target.value = "";
      return;
    }
    const toAdd = files.slice(0, remaining).filter(validateImageSize);
    if (!toAdd.length) {
      e.target.value = "";
      return;
    }
    const newImages: PackageImageInput[] = toAdd.map((file) => ({
      image: file,
    }));
    const updated = [...extraImages, ...newImages];
    setExtraImages(updated);
    setValue("images", updated);
    setValue("add_more_images", true);
    e.target.value = ""; // reset so same files can be re-selected
  };

  return (
    <section className="admin-section">
      <header className="admin-page-header mb-6">
        <div className="flex items-center gap-4">
          <Button
            variant="ghost"
            size="icon"
            onClick={() => router.back()}
            aria-label={FIX_PACKAGES_ADMIN.GO_BACK_ARIA}
            type="button"
          >
            <ArrowLeft className="h-5 w-5" />
          </Button>
          <div>
            <h1 className="admin-page-title">
              {mode === "add"
                ? FIX_PACKAGES_ADMIN.ADD_PACKAGE_TITLE
                : FIX_PACKAGES_ADMIN.EDIT_PACKAGE_TITLE}
            </h1>
            <p className="admin-page-description">
              {mode === "add"
                ? FIX_PACKAGES_ADMIN.ADD_PACKAGE_DESCRIPTION
                : FIX_PACKAGES_ADMIN.EDIT_PACKAGE_DESCRIPTION}
            </p>
          </div>
        </div>
      </header>

      <form
        onSubmit={handleSubmit(onSubmit as any, (formErrors) => {
          const itinErrors = formErrors.itineraries;
          if (itinErrors && Array.isArray(itinErrors)) {
            setCollapsedDayIds((prev) => {
              const next = new Set(prev);
              (itinErrors as any[]).forEach((err: any, idx: number) => {
                if (err && dayEntries[idx]) {
                  next.delete(dayEntries[idx].id);
                }
              });
              return next;
            });
          }
        })}
        className="space-y-6"
      >
        <div className="grid grid-cols-12 gap-6">
          <BannerSliderCard
            bannerPreview={bannerPreview}
            onBannerChange={handleBannerChange}
            extraImages={extraImages.filter((img) => img.status !== 0)}
            onSliderImagesChange={handleSliderImagesChange}
            onDeleteImage={handleDeleteImage}
          />
        </div>
        <div className="space-y-6">
          <PackageInfoCard
            register={register}
            errors={errors}
            watch={watch}
            setValue={setValue}
            isDataLoaded={isDataLoaded}
          />

          <ItineraryDetailsCard
            dayEntries={dayEntries}
            setDayEntries={setDayEntries}
            updateDay={updateDay}
            makeDefaultDay={makeDefaultDay}
            collapsedDayIds={collapsedDayIds}
            setCollapsedDayIds={setCollapsedDayIds}
            errors={errors.itineraries}
            citiesLoading={citiesLoading}
            onOpenVehicleDialog={(dayId, index) => {
              setVehicleTargetDayId(dayId);
              setEditingVehicleIndex(index);
              setIsVehicleDialogOpen(true);
            }}
            onOpenHotelDialog={(dayId, index) => {
              setHotelTargetDayId(dayId);
              setEditingHotelIndex(index);
              setIsHotelDialogOpen(true);
            }}
            onOpenSightseeingModal={(dayId, draft) => {
              setSightseeingTargetId(dayId);
              setSightseeingDraft(draft);
              setIsSightseeingModalOpen(true);
            }}
            itineraryRefs={itineraryRefs}
          />

          <OtherServicesCard
            availableServices={availableServices}
            servicesLoading={servicesLoading}
            watch={watch}
            setValue={setValue}
          />

          <TagsCard
            availableTags={availableTags}
            tagsLoading={tagsLoading}
            selectedTagIds={selectedTagIds}
            setSelectedTagIds={setSelectedTagIds}
            setValue={setValue}
            onAddTagClick={() => setIsAddTagModalOpen(true)}
          />

          <ExtrasCard
            extrasEntries={extrasEntries}
            onAddEntry={() =>
              setExtrasEntries((prev) => [
                ...prev,
                { id: Date.now(), title: "", description: "" },
              ])
            }
            onUpdateEntry={updateExtras}
            onRemoveEntry={removeExtras}
            errors={errors}
            isDataLoaded={isDataLoaded}
            extrasRefs={extrasRefs}
          />
        </div>

        <div className="flex justify-end gap-4 p-6 border-t bg-background/50 sticky bottom-0 z-10">
          <Button type="button" variant="outline" onClick={() => router.back()}>
            {FIX_PACKAGES_ADMIN.CANCEL_BUTTON}
          </Button>
          <Button type="submit" disabled={isSubmitting}>
            {isSubmitting
              ? FIX_PACKAGES_ADMIN.SAVING
              : mode === "add"
                ? FIX_PACKAGES_ADMIN.CREATE_PACKAGE_BUTTON
                : FIX_PACKAGES_ADMIN.UPDATE_PACKAGE_BUTTON}
          </Button>
        </div>
      </form>

      <VehicleDialog
        open={isVehicleDialogOpen}
        onOpenChange={setIsVehicleDialogOpen}
        onSave={
          editingVehicleIndex !== null ? handleEditVehicle : handleAddVehicle
        }
        defaultValues={
          editingVehicleIndex !== null && vehicleTargetDayId !== null
            ? dayEntries.find((d) => d.id === vehicleTargetDayId)?.vehicles[
                editingVehicleIndex
              ]
            : undefined
        }
      />

      <HotelDialog
        open={isHotelDialogOpen}
        onOpenChange={setIsHotelDialogOpen}
        onSave={editingHotelIndex !== null ? handleEditHotel : handleAddHotel}
        defaultValues={
          editingHotelIndex !== null && hotelTargetDayId !== null
            ? dayEntries.find((d) => d.id === hotelTargetDayId)?.hotels[
                editingHotelIndex
              ]
            : undefined
        }
      />

      <ImageDialog
        open={isImageDialogOpen}
        onOpenChange={setIsImageDialogOpen}
        onSave={editingImageIndex !== null ? handleEditImage : handleAddImage}
        defaultValues={
          editingImageIndex !== null
            ? extraImages.filter((img) => img.status !== 0)[editingImageIndex]
            : undefined
        }
      />

      <SightseeingModal
        open={isSightseeingModalOpen}
        onOpenChange={setIsSightseeingModalOpen}
        draft={sightseeingDraft}
        onDraftChange={setSightseeingDraft}
        onSave={(filtered) => {
          if (sightseeingTargetId !== null) {
            updateDay(sightseeingTargetId, { sightseeings: filtered });
          }
          setIsSightseeingModalOpen(false);
        }}
      />

      <AddTagModal
        open={isAddTagModalOpen}
        onOpenChange={(open) => {
          if (!open) setNewTagName("");
          setIsAddTagModalOpen(open);
        }}
        newTagName={newTagName}
        onNewTagNameChange={setNewTagName}
        isTagSaving={isTagSaving}
        onSave={async (trimmed) => {
          setIsTagSaving(true);
          await createTagAction(trimmed);
          await fetchTags();
          setIsTagSaving(false);
        }}
      />
    </section>
  );
}
