"use client";

import { useState, useRef, useCallback, useEffect } from "react";
import { motion, AnimatePresence, type Variants } from "framer-motion";
import { MapEmbed } from "@/components/marketplace/MapEmbed";
import Cookies from "js-cookie";
import apiClient from "@/lib/axiosClient";
import { API_ENDPOINTS } from "@/lib/apiEndpoints";
import {
  ArrowLeft,
  ArrowRight,
  Check,
  ChevronDown,
  Image as ImageIcon,
  Loader2,
  MapPin,
  Package,
  Tag,
  Upload,
  X,
  Zap,
  Eye,
  FileText,
  ToggleLeft,
  ToggleRight,
  AlertCircle,
  LocateFixed,
  PenLine,
  CheckCircle2,
} from "lucide-react";
import type { WizardData, ListingCondition, ListingImage } from "./types";
import { useAppDispatch, useAppSelector } from "@/store/hooks";
import { fetchMarketplaceCategories } from "@/store/slices/marketplaceCategorySlice";
import type { ClassifiedCategory } from "@/store/slices/marketplaceCategorySlice";

/* ─── Condition options ────────────────────────────────────────────────────── */

const CONDITIONS: {
  id: ListingCondition;
  label: string;
  description: string;
  icon: string;
  apiValue: number;
}[] = [
  {
    id: "new",
    label: "Brand New",
    description: "Unused, original packaging",
    icon: "✨",
    apiValue: 1,
  },

  {
    id: "used",
    label: "Used",
    description: "Visible wear, still works",
    icon: "🔧",
    apiValue: 2,
  },
];

const STEPS = [
  { id: 1, label: "Details", icon: Package },
  { id: 2, label: "Photos", icon: ImageIcon },
  { id: 3, label: "Pricing", icon: Tag },
  { id: 4, label: "Location", icon: MapPin },
  { id: 5, label: "Review", icon: Eye },
];

const INITIAL_DATA: WizardData = {
  title: "",
  description: "",
  category: "",
  categoryId: 0,
  subCategory: "",
  condition: "",
  images: [],
  price: "",
  quantity: "1",
  originalPrice: "",
  isNegotiable: false,
  city: "",
  area: "",
  pincode: "",
  latitude: "",
  longitude: "",
};

function validateStep(step: number, data: WizardData): Record<string, string> {
  const e: Record<string, string> = {};
  if (step === 1) {
    if (!data.title.trim()) e.title = "Product title is required";
    else if (data.title.trim().length < 10)
      e.title = "Title must be at least 10 characters";
    if (!data.description.trim()) e.description = "Description is required";
    else if (data.description.trim().length < 20)
      e.description = "Description must be at least 20 characters";
    if (!data.categoryId || data.categoryId === 0)
      e.category = "Please select a category";
    if (!data.condition) e.condition = "Please select a condition";
  }
  if (step === 2) {
    if (data.images.length === 0) e.images = "Add at least one photo";
  }
  if (step === 3) {
    if (!data.price || parseFloat(data.price) <= 0)
      e.price = "Enter a valid selling price";
    const qty = Number(data.quantity);
    if (!data.quantity || !Number.isInteger(qty) || qty < 1)
      e.quantity = "Enter the available quantity (minimum 1)";
  }
  if (step === 4) {
    if (!data.city.trim())
      e.city = "City is required — buyers search by location";
  }
  return e;
}

function categoryEmoji(name: string): string {
  const n = name.toLowerCase();
  if (n.includes("electron") || n.includes("mobile") || n.includes("phone"))
    return "📱";
  if (n.includes("fashion") || n.includes("cloth") || n.includes("wear"))
    return "👗";
  if (n.includes("vehicle") || n.includes("car") || n.includes("bike"))
    return "🚗";
  if (n.includes("furnitur") || n.includes("sofa") || n.includes("bed"))
    return "🪑";
  if (n.includes("book")) return "📚";
  if (n.includes("sport") || n.includes("fitness")) return "⚽";
  if (n.includes("home") || n.includes("kitchen") || n.includes("garden"))
    return "🏡";
  if (n.includes("beauty") || n.includes("cosmetic")) return "💄";
  if (n.includes("toy") || n.includes("kid")) return "🧸";
  return "📦";
}

/* ─── Slide variants ────────────────────────────────────────────────────────── */

const slideVariants: Variants = {
  enter: (dir: number) => ({ x: dir > 0 ? 60 : -60, opacity: 0 }),
  center: {
    x: 0,
    opacity: 1,
    transition: { type: "spring", stiffness: 300, damping: 30 },
  },
  exit: (dir: number) => ({
    x: dir > 0 ? -60 : 60,
    opacity: 0,
    transition: { duration: 0.18, ease: "easeIn" },
  }),
};

/* ─── Shared UI ─────────────────────────────────────────────────────────────── */

function Field({
  label,
  children,
  hint,
  error,
}: {
  label: string;
  children: React.ReactNode;
  hint?: string;
  error?: string;
}) {
  return (
    <div className="flex flex-col gap-1.5">
      <label className="text-xs font-semibold uppercase tracking-widest text-[var(--text-muted)]">
        {label}
      </label>
      {children}
      {error ? (
        <p className="text-xs text-[#fc0000] flex items-center gap-1 mt-0.5">
          <AlertCircle size={11} className="shrink-0" />
          {error}
        </p>
      ) : hint ? (
        <p className="text-xs text-[var(--text-muted)]">{hint}</p>
      ) : null}
    </div>
  );
}

function Input({
  value,
  onChange,
  placeholder,
  type = "text",
  prefix,
  className = "",
  hasError = false,
}: {
  value: string;
  onChange: (v: string) => void;
  placeholder?: string;
  type?: string;
  prefix?: string;
  className?: string;
  hasError?: boolean;
}) {
  return (
    <div className="relative">
      {prefix && (
        <span className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[var(--text-muted)] font-semibold text-lg pointer-events-none select-none">
          {prefix}
        </span>
      )}
      <input
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        className={`w-full rounded-xl border bg-[var(--card)] px-4 py-3.5 text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none transition-all duration-200 focus:ring-2 ${
          hasError
            ? "border-[#fc0000] ring-1 ring-[rgba(252,0,0,0.12)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.2)]"
            : "border-[var(--border)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.12)]"
        } ${prefix ? "pl-10" : ""} ${className}`}
      />
    </div>
  );
}

function Textarea({
  value,
  onChange,
  placeholder,
  rows = 4,
  hasError = false,
}: {
  value: string;
  onChange: (v: string) => void;
  placeholder?: string;
  rows?: number;
  hasError?: boolean;
}) {
  return (
    <textarea
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      rows={rows}
      className={`w-full rounded-xl border bg-[var(--card)] px-4 py-3.5 text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none transition-all duration-200 focus:ring-2 resize-none ${
        hasError
          ? "border-[#fc0000] ring-1 ring-[rgba(252,0,0,0.12)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.2)]"
          : "border-[var(--border)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.12)]"
      }`}
    />
  );
}

/* ─── Step 1: Product Info ──────────────────────────────────────────────────── */

function Step1({
  data,
  update,
  apiCategories,
  errors = {},
}: {
  data: WizardData;
  update: (patch: Partial<WizardData>) => void;
  apiCategories: ClassifiedCategory[];
  errors?: Record<string, string>;
}) {
  return (
    <div className="space-y-6">
      <Field
        label="Product Title"
        hint={
          !errors.title
            ? "Be specific — good titles get 3× more views"
            : undefined
        }
        error={errors.title}
      >
        <Input
          value={data.title}
          onChange={(v) => update({ title: v })}
          placeholder="e.g. iPhone 14 Pro Max 256GB Deep Purple"
          hasError={!!errors.title}
        />
      </Field>

      <Field
        label="Description"
        hint={
          !errors.description
            ? "Mention key features, defects, and reason for selling"
            : undefined
        }
        error={errors.description}
      >
        <Textarea
          value={data.description}
          onChange={(v) => update({ description: v })}
          placeholder="Describe your item in detail. Include model, specs, accessories included, any damage or wear..."
          rows={5}
          hasError={!!errors.description}
        />
      </Field>

      <Field label="Category" error={errors.category}>
        {apiCategories.length === 0 ? (
          <div className="flex items-center gap-2 py-4 text-[var(--text-muted)]">
            <Loader2 size={16} className="animate-spin" />
            <span className="text-sm">Loading categories…</span>
          </div>
        ) : (
          <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-2">
            {apiCategories.map((cat) => (
              <motion.button
                key={cat.id}
                type="button"
                whileHover={{ scale: 1.02 }}
                whileTap={{ scale: 0.98 }}
                onClick={() =>
                  update({ category: cat.name, categoryId: cat.id })
                }
                className={`relative flex flex-col items-center gap-2 rounded-xl border p-3 text-center transition-all duration-200 cursor-pointer ${
                  data.categoryId === cat.id
                    ? "border-[#fc0000] bg-[rgba(252,0,0,0.06)] text-[#fc0000]"
                    : "border-[var(--border)] bg-[var(--card)] text-[var(--text-secondary)] hover:border-[var(--border-soft)] hover:bg-[var(--deep)]"
                }`}
              >
                {data.categoryId === cat.id && (
                  <span className="absolute top-1.5 right-1.5 w-4 h-4 rounded-full bg-[#fc0000] flex items-center justify-center">
                    <Check size={10} className="text-white" />
                  </span>
                )}
                <span className="text-2xl leading-none">
                  {categoryEmoji(cat.name)}
                </span>
                <span className="text-xs font-medium leading-tight">
                  {cat.name}
                </span>
              </motion.button>
            ))}
          </div>
        )}
      </Field>

      <Field label="Condition" error={errors.condition}>
        <div className="grid grid-cols-2 gap-3">
          {CONDITIONS.map((c) => (
            <motion.button
              key={c.id}
              type="button"
              whileHover={{ scale: 1.02 }}
              whileTap={{ scale: 0.97 }}
              onClick={() => update({ condition: c.id })}
              className={`relative flex items-start gap-3 rounded-xl border p-4 text-left transition-all duration-200 cursor-pointer ${
                data.condition === c.id
                  ? "border-[#fc0000] bg-[rgba(252,0,0,0.06)]"
                  : errors.condition
                    ? "border-[#fc0000] border-opacity-50 bg-[var(--card)] hover:border-[#fc0000]"
                    : "border-[var(--border)] bg-[var(--card)] hover:border-[rgba(252,0,0,0.3)]"
              }`}
            >
              <span className="text-2xl leading-none mt-0.5 flex-shrink-0">
                {c.icon}
              </span>
              <div className="min-w-0">
                <p
                  className={`text-sm font-semibold ${data.condition === c.id ? "text-[#fc0000]" : "text-[var(--text-primary)]"}`}
                >
                  {c.label}
                </p>
                <p className="text-xs text-[var(--text-muted)] mt-0.5 leading-tight">
                  {c.description}
                </p>
              </div>
              {data.condition === c.id && (
                <span className="absolute top-2 right-2 w-5 h-5 rounded-full bg-[#fc0000] flex items-center justify-center flex-shrink-0">
                  <Check size={11} className="text-white" />
                </span>
              )}
            </motion.button>
          ))}
        </div>
      </Field>
    </div>
  );
}

/* ─── Step 2: Photos ────────────────────────────────────────────────────────── */

function Step2({
  data,
  update,
  errors = {},
}: {
  data: WizardData;
  update: (patch: Partial<WizardData>) => void;
  errors?: Record<string, string>;
}) {
  const fileRef = useRef<HTMLInputElement>(null);
  const [dragging, setDragging] = useState(false);

  const addFiles = useCallback(
    (files: FileList | null) => {
      if (!files) return;
      const remaining = 8 - data.images.length;
      const toAdd = Array.from(files).slice(0, remaining);
      const newImages: ListingImage[] = toAdd.map((file) => ({
        id: `${Date.now()}-${Math.random()}`,
        url: URL.createObjectURL(file),
        isCover: data.images.length === 0,
        file,
      }));
      const allImages = [...data.images, ...newImages];
      if (!allImages.some((img) => img.isCover) && allImages.length > 0) {
        allImages[0].isCover = true;
      }
      update({ images: allImages });
    },
    [data.images, update],
  );

  const removeImage = (id: string) => {
    const filtered = data.images.filter((img) => img.id !== id);
    if (filtered.length > 0 && !filtered.some((img) => img.isCover)) {
      filtered[0].isCover = true;
    }
    update({ images: filtered });
  };

  const setCover = (id: string) => {
    update({
      images: data.images.map((img) => ({ ...img, isCover: img.id === id })),
    });
  };

  return (
    <div className="space-y-5">
      <div className="flex items-center justify-between">
        <div>
          <h3 className="text-sm font-semibold text-[var(--text-primary)]">
            Product Photos
          </h3>
          <p className="text-xs text-[var(--text-muted)] mt-0.5">
            Add up to 8 photos · First photo is the cover
          </p>
        </div>
        <span className="text-xs font-medium text-[var(--text-muted)] bg-[var(--card)] border border-[var(--border)] rounded-full px-2.5 py-1">
          {data.images.length} / 8
        </span>
      </div>

      {data.images.length < 8 && (
        <motion.button
          type="button"
          whileHover={{ scale: 1.005 }}
          whileTap={{ scale: 0.995 }}
          onClick={() => fileRef.current?.click()}
          onDragEnter={(e) => {
            e.preventDefault();
            setDragging(true);
          }}
          onDragLeave={() => setDragging(false)}
          onDragOver={(e) => e.preventDefault()}
          onDrop={(e) => {
            e.preventDefault();
            setDragging(false);
            addFiles(e.dataTransfer.files);
          }}
          className={`relative w-full rounded-2xl border-2 border-dashed transition-all duration-200 p-10 flex flex-col items-center gap-3 cursor-pointer ${
            dragging
              ? "border-[#fc0000] bg-[rgba(252,0,0,0.06)]"
              : errors.images
                ? "border-[#fc0000] bg-[rgba(252,0,0,0.04)]"
                : "border-[var(--border)] bg-[var(--card)] hover:border-[rgba(252,0,0,0.4)] hover:bg-[rgba(252,0,0,0.03)]"
          }`}
        >
          <div
            className={`w-14 h-14 rounded-2xl flex items-center justify-center transition-all ${dragging ? "bg-[rgba(252,0,0,0.15)]" : "bg-[var(--deep)]"}`}
          >
            <Upload
              size={24}
              className={
                dragging ? "text-[#fc0000]" : "text-[var(--text-muted)]"
              }
            />
          </div>
          <div className="text-center">
            <p
              className={`text-sm font-semibold ${dragging ? "text-[#fc0000]" : "text-[var(--text-primary)]"}`}
            >
              {dragging ? "Drop your photos here" : "Upload Photos"}
            </p>
            <p className="text-xs text-[var(--text-muted)] mt-1">
              Drag & drop or click to browse · JPG, PNG, WEBP
            </p>
          </div>
        </motion.button>
      )}

      {errors.images && (
        <p className="text-xs text-[#fc0000] flex items-center gap-1">
          <AlertCircle size={11} className="shrink-0" />
          {errors.images}
        </p>
      )}

      <input
        ref={fileRef}
        type="file"
        accept="image/*"
        multiple
        className="hidden"
        onChange={(e) => addFiles(e.target.files)}
      />

      {data.images.length > 0 && (
        <div className="grid grid-cols-4 gap-2">
          <AnimatePresence>
            {data.images.map((img) => (
              <motion.div
                key={img.id}
                initial={{ opacity: 0, scale: 0.8 }}
                animate={{ opacity: 1, scale: 1 }}
                exit={{ opacity: 0, scale: 0.8 }}
                transition={{ type: "spring", stiffness: 300, damping: 25 }}
                className="relative group aspect-square rounded-xl overflow-hidden border-2 transition-all duration-200 cursor-pointer"
                style={{
                  borderColor: img.isCover ? "#fc0000" : "var(--border)",
                }}
                onClick={() => setCover(img.id)}
              >
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img
                  src={img.url}
                  alt="listing"
                  className="w-full h-full object-cover"
                />
                {img.isCover && (
                  <div className="absolute bottom-0 left-0 right-0 bg-[rgba(252,0,0,0.9)] text-white text-[9px] font-bold uppercase tracking-wider text-center py-1">
                    Cover
                  </div>
                )}
                <button
                  type="button"
                  onClick={(e) => {
                    e.stopPropagation();
                    removeImage(img.id);
                  }}
                  className="absolute top-1 right-1 w-6 h-6 rounded-full bg-black/70 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
                >
                  <X size={10} className="text-white" />
                </button>
              </motion.div>
            ))}
          </AnimatePresence>
        </div>
      )}

      {data.images.length > 0 && (
        <p className="text-xs text-[var(--text-muted)] flex items-center gap-1.5">
          <span className="w-3 h-3 rounded-full border-2 border-[#fc0000] inline-block" />
          Tap any photo to set it as cover
        </p>
      )}
    </div>
  );
}

/* ─── Step 3: Pricing ───────────────────────────────────────────────────────── */

function Step3({
  data,
  update,
  errors = {},
  currencyCode,
}: {
  data: WizardData;
  update: (patch: Partial<WizardData>) => void;
  errors?: Record<string, string>;
  currencyCode: string;
}) {
  const price = parseFloat(data.price) || 0;
  const originalPrice = parseFloat(data.originalPrice) || 0;
  const discount =
    originalPrice > 0 && price > 0 && originalPrice > price
      ? Math.round(((originalPrice - price) / originalPrice) * 100)
      : 0;

  return (
    <div className="space-y-6">
      <Field
        label="Selling Price"
        hint={
          !errors.price ? "Set a competitive price to sell faster" : undefined
        }
        error={errors.price}
      >
        <div className="relative">
          <span className="absolute left-4 top-1/2 -translate-y-1/2 text-2xl font-bold text-[var(--text-muted)] select-none pointer-events-none">
            {currencyCode}
          </span>
          <input
            type="number"
            value={data.price}
            onChange={(e) => update({ price: e.target.value })}
            placeholder="0"
            min="0"
            className={`w-full rounded-xl border bg-[var(--card)] pl-10 pr-4 py-4 text-2xl font-bold text-[var(--text-primary)] placeholder:text-[var(--text-muted)] outline-none transition-all duration-200 focus:ring-2 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${
              errors.price
                ? "border-[#fc0000] ring-1 ring-[rgba(252,0,0,0.12)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.2)]"
                : "border-[var(--border)] focus:border-[#fc0000] focus:ring-[rgba(252,0,0,0.12)]"
            }`}
          />
        </div>
      </Field>

      <Field
        label="Quantity Available"
        hint={
          !errors.quantity ? "How many units do you have for sale?" : undefined
        }
        error={errors.quantity}
      >
        <Input
          value={data.quantity}
          onChange={(v) => update({ quantity: v.replace(/\D/g, "") })}
          placeholder="1"
          type="number"
        />
      </Field>

      <div className="flex items-center justify-between rounded-xl border border-[var(--border)] bg-[var(--card)] px-4 py-3.5">
        <div>
          <p className="text-sm font-semibold text-[var(--text-primary)]">
            Price is Negotiable
          </p>
          <p className="text-xs text-[var(--text-muted)] mt-0.5">
            Buyers can send you a price offer
          </p>
        </div>
        <button
          type="button"
          onClick={() => update({ isNegotiable: !data.isNegotiable })}
          className="flex-shrink-0"
        >
          {data.isNegotiable ? (
            <ToggleRight size={32} className="text-[#fc0000]" />
          ) : (
            <ToggleLeft size={32} className="text-[var(--text-muted)]" />
          )}
        </button>
      </div>

      <Field
        label="Original Price (Optional)"
        hint="Shows discount badge on your listing"
      >
        <Input
          value={data.originalPrice}
          onChange={(v) => update({ originalPrice: v })}
          placeholder="0"
          type="number"
          prefix={currencyCode}
        />
      </Field>

      {price > 0 && (
        <motion.div
          initial={{ opacity: 0, y: 8 }}
          animate={{ opacity: 1, y: 0 }}
          className="rounded-xl border border-[var(--border)] bg-[var(--card)] overflow-hidden"
        >
          <div className="px-4 py-3 border-b border-[var(--border)] flex items-center gap-2">
            <Eye size={14} className="text-[var(--text-muted)]" />
            <span className="text-xs font-semibold uppercase tracking-widest text-[var(--text-muted)]">
              Buyer Preview
            </span>
          </div>
          <div className="p-4 flex items-start justify-between gap-4">
            <div>
              <div className="flex items-baseline gap-2 flex-wrap">
                <span className="text-2xl font-bold text-[var(--text-primary)]">
                  {currencyCode}
                  {price.toLocaleString()}
                </span>
                {originalPrice > price && (
                  <span className="text-sm text-[var(--text-muted)] line-through">
                    {currencyCode}
                    {originalPrice.toLocaleString()}
                  </span>
                )}
              </div>
              <div className="flex items-center gap-2 mt-1.5 flex-wrap">
                {discount > 0 && (
                  <span className="text-xs font-bold text-white bg-[#fc0000] px-1.5 py-0.5 rounded-md">
                    {discount}% OFF
                  </span>
                )}
                {data.isNegotiable && (
                  <span className="text-xs font-medium text-[#f0c040] bg-[rgba(240,192,64,0.12)] border border-[rgba(240,192,64,0.3)] px-2 py-0.5 rounded-full">
                    Negotiable
                  </span>
                )}
              </div>
            </div>
            <div className="flex-shrink-0 text-right">
              {data.condition && (
                <span className="text-xs font-medium px-2.5 py-1 rounded-full bg-[var(--deep)] border border-[var(--border)] text-[var(--text-secondary)]">
                  {CONDITIONS.find((c) => c.id === data.condition)?.label}
                </span>
              )}
            </div>
          </div>
        </motion.div>
      )}
    </div>
  );
}

/* ─── Step 4: Location ──────────────────────────────────────────────────────── */

const CITIES = [
  "Mumbai",
  "Delhi",
  "Bangalore",
  "Chennai",
  "Hyderabad",
  "Pune",
  "Kolkata",
  "Ahmedabad",
  "Surat",
  "Jaipur",
  "Lucknow",
  "Kanpur",
  "Nagpur",
  "Visakhapatnam",
  "Patna",
  "Bhopal",
  "Indore",
  "Vadodara",
  "Coimbatore",
  "Kochi",
];

function Step4({
  data,
  update,
  errors = {},
}: {
  data: WizardData;
  update: (patch: Partial<WizardData>) => void;
  errors?: Record<string, string>;
}) {
  /* "gps" = detected via browser; "manual" = typed by user */
  const [mode, setMode] = useState<"gps" | "manual">(
    data.latitude && data.longitude ? "gps" : "manual",
  );
  const [locating, setLocating] = useState(false);
  const [gpsError, setGpsError] = useState("");
  const gpsDetected = !!(data.latitude && data.longitude);

  const switchMode = (next: "gps" | "manual") => {
    setMode(next);
    setGpsError("");
    if (next === "manual") {
      /* Clear GPS coordinates — manual city/area/pincode stay editable */
      update({ latitude: "", longitude: "" });
    }
  };

  const detectLocation = async () => {
    if (!navigator.geolocation) {
      setGpsError("Geolocation is not supported by your browser.");
      return;
    }
    setLocating(true);
    setGpsError("");

    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        const lat = pos.coords.latitude.toFixed(6);
        const lng = pos.coords.longitude.toFixed(6);
        try {
          const res = await fetch(
            `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`,
            { headers: { "Accept-Language": "en" } },
          );
          const geo = await res.json();
          const addr = geo.address ?? {};
          const detectedCity =
            addr.city || addr.town || addr.county || addr.state_district || "";
          /* Match against dropdown list (case-insensitive) */
          const matchedCity =
            CITIES.find(
              (c) => c.toLowerCase() === detectedCity.toLowerCase(),
            ) ?? detectedCity;
          const detectedArea =
            addr.suburb ||
            addr.neighbourhood ||
            addr.village ||
            addr.hamlet ||
            "";
          const detectedPincode = addr.postcode ?? "";
          update({
            latitude: lat,
            longitude: lng,
            city: matchedCity,
            area: detectedArea,
            pincode: detectedPincode,
          });
        } catch {
          /* Reverse geocoding failed — save coords only */
          update({ latitude: lat, longitude: lng });
        }
        setLocating(false);
      },
      (err) => {
        setLocating(false);
        if (err.code === 1)
          setGpsError(
            "Location access was denied. Allow location in your browser settings or enter manually.",
          );
        else if (err.code === 2)
          setGpsError(
            "Location unavailable. Try again or switch to manual entry.",
          );
        else setGpsError("Location detection timed out. Try again.");
      },
      { timeout: 10000 },
    );
  };

  const resetGps = () => {
    update({ latitude: "", longitude: "", city: "", area: "", pincode: "" });
    setGpsError("");
  };

  return (
    <div className="space-y-5">
      {/* Header */}
      <div
        className="rounded-xl border p-4 flex items-start gap-3"
        style={{ background: "var(--card)", borderColor: "var(--border)" }}
      >
        <div
          className="w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5"
          style={{ background: "rgba(252,0,0,0.1)" }}
        >
          <MapPin size={18} className="text-[#fc0000]" />
        </div>
        <div>
          <p
            className="text-sm font-semibold"
            style={{ color: "var(--text-primary)" }}
          >
            Your Listing Location
          </p>
          <p className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>
            Buyers nearby will discover your listing. Your exact address is
            never shown.
          </p>
        </div>
      </div>

      {/* Mode toggle */}
      <div
        className="flex rounded-xl border p-1 gap-1"
        style={{ background: "var(--deep)", borderColor: "var(--border)" }}
      >
        {(
          [
            {
              id: "gps" as const,
              label: "Current Location",
              Icon: LocateFixed,
            },
            { id: "manual" as const, label: "Enter Manually", Icon: PenLine },
          ] as const
        ).map(({ id, label, Icon }) => (
          <button
            key={id}
            type="button"
            onClick={() => switchMode(id)}
            className="flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-semibold transition-all duration-200"
            style={{
              background: mode === id ? "var(--card)" : "transparent",
              color: mode === id ? "var(--text-primary)" : "var(--text-muted)",
              boxShadow: mode === id ? "0 1px 4px rgba(0,0,0,0.15)" : "none",
            }}
          >
            <Icon size={14} />
            {label}
          </button>
        ))}
      </div>

      {/* ── GPS mode ── */}
      <AnimatePresence mode="wait">
        {mode === "gps" && (
          <motion.div
            key="gps-panel"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -6 }}
            transition={{ duration: 0.18 }}
            className="space-y-4"
          >
            {!gpsDetected ? (
              /* ─ Detect button ─ */
              <motion.button
                type="button"
                onClick={detectLocation}
                disabled={locating}
                whileTap={{ scale: 0.98 }}
                className="w-full flex flex-col items-center justify-center gap-3 py-10 rounded-2xl border-2 border-dashed transition-all duration-200"
                style={{
                  borderColor: locating ? "#fc0000" : "var(--border)",
                  background: locating ? "rgba(252,0,0,0.04)" : "var(--card)",
                }}
              >
                <div
                  className="w-14 h-14 rounded-full flex items-center justify-center"
                  style={{ background: "rgba(252,0,0,0.1)" }}
                >
                  {locating ? (
                    <Loader2
                      size={26}
                      className="text-[#fc0000] animate-spin"
                    />
                  ) : (
                    <LocateFixed size={26} className="text-[#fc0000]" />
                  )}
                </div>
                <div className="text-center">
                  <p
                    className="text-sm font-bold"
                    style={{ color: "var(--text-primary)" }}
                  >
                    {locating
                      ? "Detecting your location…"
                      : "Detect My Location"}
                  </p>
                  <p
                    className="text-xs mt-1"
                    style={{ color: "var(--text-muted)" }}
                  >
                    {locating
                      ? "This may take a moment"
                      : "Auto-fills city, area & pincode via GPS"}
                  </p>
                </div>
              </motion.button>
            ) : (
              /* ─ Detected result card ─ */
              <motion.div
                initial={{ opacity: 0, scale: 0.98 }}
                animate={{ opacity: 1, scale: 1 }}
                className="rounded-xl border"
                style={{
                  background: "var(--card)",
                  borderColor: "var(--border)",
                }}
              >
                {/* Success header */}
                <div
                  className="flex items-start justify-between gap-3 p-4 rounded-t-xl"
                  style={{ background: "rgba(34,197,94,0.07)" }}
                >
                  <div className="flex items-center gap-2.5">
                    <CheckCircle2
                      size={18}
                      className="text-[#22c55e] flex-shrink-0"
                    />
                    <div>
                      <p
                        className="text-xs font-bold uppercase tracking-wider"
                        style={{ color: "#22c55e" }}
                      >
                        Location Detected
                      </p>
                      <p
                        className="text-sm font-semibold mt-0.5"
                        style={{ color: "var(--text-primary)" }}
                      >
                        {[data.area, data.city].filter(Boolean).join(", ") ||
                          `${data.latitude}, ${data.longitude}`}
                      </p>
                      {data.pincode && (
                        <p
                          className="text-xs mt-0.5"
                          style={{ color: "var(--text-muted)" }}
                        >
                          Pincode: {data.pincode}
                        </p>
                      )}
                    </div>
                  </div>
                  <button
                    type="button"
                    onClick={resetGps}
                    className="shrink-0 text-xs font-semibold px-2.5 py-1 rounded-lg border transition-all"
                    style={{
                      borderColor: "var(--border)",
                      color: "var(--text-muted)",
                      background: "var(--deep)",
                    }}
                  >
                    Reset
                  </button>
                </div>

                {/* Refine section */}
                <div
                  className="p-4 space-y-4 border-t"
                  style={{ borderColor: "var(--border)" }}
                >
                  <p
                    className="text-xs font-semibold"
                    style={{ color: "var(--text-muted)" }}
                  >
                    Refine if needed
                  </p>
                  <Field label="Area / Locality">
                    <Input
                      value={data.area}
                      onChange={(v) => update({ area: v })}
                      placeholder="e.g. Andheri West, Koramangala…"
                    />
                  </Field>
                  <Field label="Pincode">
                    <Input
                      value={data.pincode}
                      onChange={(v) => update({ pincode: v })}
                      placeholder="400001"
                    />
                  </Field>
                </div>
              </motion.div>
            )}

            {/* Validation error: city required but not detected yet */}
            {!gpsDetected && errors.city && !gpsError && (
              <motion.p
                initial={{ opacity: 0, y: 4 }}
                animate={{ opacity: 1, y: 0 }}
                className="text-xs text-[#fc0000] flex items-center gap-1"
              >
                <AlertCircle size={11} className="shrink-0" />
                {errors.city}
              </motion.p>
            )}

            {/* Error state */}
            {gpsError && (
              <motion.div
                initial={{ opacity: 0, y: 4 }}
                animate={{ opacity: 1, y: 0 }}
                className="space-y-2"
              >
                <div
                  className="flex items-start gap-2.5 px-3.5 py-3 rounded-xl"
                  style={{
                    background: "rgba(252,0,0,0.06)",
                    border: "1px solid rgba(252,0,0,0.2)",
                  }}
                >
                  <AlertCircle
                    size={15}
                    className="text-[#fc0000] shrink-0 mt-0.5"
                  />
                  <p className="text-xs" style={{ color: "var(--text-muted)" }}>
                    {gpsError}
                  </p>
                </div>
                <button
                  type="button"
                  onClick={() => switchMode("manual")}
                  className="w-full text-xs font-semibold py-1.5"
                  style={{ color: "var(--accent)" }}
                >
                  Switch to manual entry →
                </button>
              </motion.div>
            )}
          </motion.div>
        )}

        {/* ── Manual mode ── */}
        {mode === "manual" && (
          <motion.div
            key="manual-panel"
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -6 }}
            transition={{ duration: 0.18 }}
            className="space-y-4"
          >
            <Field label="City" error={errors.city}>
              <div className="relative">
                <select
                  value={data.city}
                  onChange={(e) => update({ city: e.target.value })}
                  className="w-full appearance-none rounded-xl border px-4 py-3.5 pr-10 outline-none transition-all duration-200 focus:ring-2 cursor-pointer"
                  style={{
                    background: "var(--card)",
                    borderColor: errors.city ? "#fc0000" : "var(--border)",
                    color: data.city
                      ? "var(--text-primary)"
                      : "var(--text-muted)",
                    boxShadow: errors.city
                      ? "0 0 0 1px rgba(252,0,0,0.15)"
                      : undefined,
                  }}
                >
                  <option value="">Select your city</option>
                  {CITIES.map((city) => (
                    <option key={city} value={city}>
                      {city}
                    </option>
                  ))}
                </select>
                <ChevronDown
                  size={16}
                  className="absolute right-3.5 top-1/2 -translate-y-1/2 pointer-events-none"
                  style={{ color: "var(--text-muted)" }}
                />
              </div>
            </Field>

            <Field label="Area / Locality">
              <Input
                value={data.area}
                onChange={(v) => update({ area: v })}
                placeholder="e.g. Andheri West, Koramangala…"
              />
            </Field>

            <Field label="Pincode">
              <Input
                value={data.pincode}
                onChange={(v) => update({ pincode: v })}
                placeholder="400001"
                type="text"
              />
            </Field>
          </motion.div>
        )}
      </AnimatePresence>

      {/* Shared map preview */}
      {(data.city || data.area) && (
        <motion.div
          initial={{ opacity: 0, y: 6 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.2 }}
        >
          <MapEmbed
            area={data.area}
            city={data.city}
            lat={data.latitude ? parseFloat(data.latitude) : undefined}
            lng={data.longitude ? parseFloat(data.longitude) : undefined}
            height={200}
          />
          <div className="mt-2 flex items-center gap-2 px-1">
            <MapPin size={13} className="text-[#fc0000] flex-shrink-0" />
            <p className="text-xs" style={{ color: "var(--text-muted)" }}>
              {[data.area, data.city, data.pincode].filter(Boolean).join(" · ")}
            </p>
          </div>
        </motion.div>
      )}
    </div>
  );
}

/* ─── Step 5: Review & Publish ──────────────────────────────────────────────── */

function Step5({
  data,
  onPublish,
  onDraft,
  isPublishing,
  error,
  isEditMode,
  currencyCode,
}: {
  data: WizardData;
  onPublish: () => void;
  onDraft: () => void;
  isPublishing: boolean;
  error: string | null;
  isEditMode: boolean;
  currencyCode: string;
}) {
  const price = parseFloat(data.price) || 0;
  const originalPrice = parseFloat(data.originalPrice) || 0;
  const discount =
    originalPrice > price && price > 0
      ? Math.round(((originalPrice - price) / originalPrice) * 100)
      : 0;
  const coverImage = data.images.find((img) => img.isCover) ?? data.images[0];

  return (
    <div className="space-y-5">
      <div className="flex items-center gap-2 text-[var(--text-muted)] text-xs mb-2">
        <Eye size={14} />
        <span>Preview as buyers will see your listing</span>
      </div>

      {/* Listing preview card */}
      <div className="rounded-2xl border border-[var(--border)] bg-[var(--card)] overflow-hidden">
        <div className="aspect-video bg-[var(--deep)] relative">
          {coverImage ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img
              src={coverImage.url}
              alt="cover"
              className="w-full h-full object-cover"
            />
          ) : (
            <div className="w-full h-full flex items-center justify-center">
              <ImageIcon size={32} className="text-[var(--text-muted)]" />
            </div>
          )}
          {data.images.length > 1 && (
            <div className="absolute bottom-3 right-3 bg-black/60 text-white text-xs font-medium px-2 py-1 rounded-full backdrop-blur-sm">
              +{data.images.length - 1} photos
            </div>
          )}
          {discount > 0 && (
            <div className="absolute top-3 left-3 bg-[#fc0000] text-white text-xs font-bold px-2 py-0.5 rounded-md">
              {discount}% OFF
            </div>
          )}
        </div>

        {data.images.length > 1 && (
          <div className="flex gap-1.5 px-3 py-2 overflow-x-auto scrollbar-hide bg-[var(--deep)]">
            {data.images.slice(0, 5).map((img) => (
              // eslint-disable-next-line @next/next/no-img-element
              <img
                key={img.id}
                src={img.url}
                alt="thumb"
                className="w-12 h-12 rounded-lg object-cover flex-shrink-0 border-2"
                style={{ borderColor: img.isCover ? "#fc0000" : "transparent" }}
              />
            ))}
          </div>
        )}

        <div className="p-4 space-y-3">
          <div className="flex items-start justify-between gap-2">
            <h2 className="text-base font-bold text-[var(--text-primary)] line-clamp-2 flex-1">
              {data.title || "Product Title"}
            </h2>
            {data.condition && (
              <span className="text-xs font-medium px-2 py-0.5 rounded-full bg-[var(--deep)] border border-[var(--border)] text-[var(--text-secondary)] flex-shrink-0">
                {CONDITIONS.find((c) => c.id === data.condition)?.label}
              </span>
            )}
          </div>

          <div className="flex items-baseline gap-2 flex-wrap">
            <span className="text-2xl font-black text-[var(--text-primary)]">
              {price > 0
                ? `${currencyCode}${price.toLocaleString()}`
                : "Price not set"}
            </span>
            {originalPrice > price && price > 0 && (
              <span className="text-sm text-[var(--text-muted)] line-through">
                {currencyCode}
                {originalPrice.toLocaleString()}
              </span>
            )}
            {data.isNegotiable && (
              <span className="text-xs text-[#f0c040] font-medium">
                Negotiable
              </span>
            )}
          </div>

          {data.category && (
            <span className="text-xs text-[var(--text-muted)] bg-[var(--deep)] px-2 py-0.5 rounded-full border border-[var(--border)]">
              {data.category}
            </span>
          )}

          {data.description && (
            <p className="text-sm text-[var(--text-secondary)] line-clamp-3 leading-relaxed">
              {data.description}
            </p>
          )}

          {(data.city || data.area) && (
            <div className="flex items-center gap-1.5 text-xs text-[var(--text-muted)]">
              <MapPin size={12} className="text-[#fc0000]" />
              <span>{[data.area, data.city].filter(Boolean).join(", ")}</span>
            </div>
          )}
        </div>
      </div>

      {/* Completeness checklist */}
      <div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-4">
        <p className="text-xs font-semibold uppercase tracking-widest text-[var(--text-muted)] mb-3">
          Listing Completeness
        </p>
        <div className="space-y-2">
          {[
            { label: "Title added", done: !!data.title },
            { label: "Description added", done: !!data.description },
            { label: "Category selected", done: data.categoryId > 0 },
            { label: "Condition specified", done: !!data.condition },
            { label: "Photos uploaded", done: data.images.length > 0 },
            { label: "Price set", done: !!data.price },
            { label: "Location added", done: !!data.city },
          ].map((item) => (
            <div key={item.label} className="flex items-center gap-2.5">
              <div
                className={`w-4 h-4 rounded-full flex items-center justify-center flex-shrink-0 ${item.done ? "bg-[#fc0000]" : "border border-[var(--border)] bg-[var(--deep)]"}`}
              >
                {item.done && <Check size={9} className="text-white" />}
              </div>
              <span
                className={`text-xs ${item.done ? "text-[var(--text-primary)]" : "text-[var(--text-muted)]"}`}
              >
                {item.label}
              </span>
            </div>
          ))}
        </div>
      </div>

      {error && (
        <div className="flex items-start gap-2 p-3 rounded-xl bg-[rgba(252,0,0,0.08)] border border-[rgba(252,0,0,0.2)]">
          <AlertCircle size={16} className="text-[#fc0000] shrink-0 mt-0.5" />
          <p className="text-xs text-[#fc0000]">{error}</p>
        </div>
      )}

      <div className="flex gap-3">
        {!isEditMode && (
          <motion.button
            type="button"
            whileHover={{ scale: 1.02 }}
            whileTap={{ scale: 0.97 }}
            onClick={onDraft}
            className="flex-1 flex items-center justify-center gap-2 rounded-xl border border-[var(--border)] bg-[var(--card)] px-4 py-3.5 text-sm font-semibold text-[var(--text-primary)] transition-all hover:bg-[var(--deep)]"
          >
            <FileText size={16} />
            Save Draft
          </motion.button>
        )}
        <motion.button
          type="button"
          whileHover={{ scale: 1.02 }}
          whileTap={{ scale: 0.97 }}
          onClick={onPublish}
          disabled={isPublishing}
          className={`flex items-center justify-center gap-2 rounded-xl bg-[#fc0000] hover:bg-[#ff4040] active:bg-[#c80000] px-4 py-3.5 text-sm font-bold text-white transition-all disabled:opacity-60 shadow-lg shadow-[rgba(252,0,0,0.25)] ${isEditMode ? "w-full" : "flex-[2]"}`}
        >
          {isPublishing ? (
            <>
              <motion.div
                animate={{ rotate: 360 }}
                transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
                className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full"
              />
              {isEditMode ? "Saving…" : "Publishing…"}
            </>
          ) : (
            <>
              <Zap size={16} />
              {isEditMode ? "Save Changes" : "Publish Listing"}
            </>
          )}
        </motion.button>
      </div>
    </div>
  );
}

/* ─── Success Screen ────────────────────────────────────────────────────────── */

function SuccessScreen({
  onDashboard,
  isEditMode,
}: {
  onDashboard: () => void;
  isEditMode: boolean;
}) {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      transition={{ type: "spring", stiffness: 260, damping: 22 }}
      className="flex flex-col items-center justify-center min-h-[60vh] text-center px-6"
    >
      <motion.div
        initial={{ scale: 0 }}
        animate={{ scale: 1 }}
        transition={{ type: "spring", stiffness: 260, damping: 18, delay: 0.1 }}
        className="w-20 h-20 rounded-full bg-[rgba(252,0,0,0.1)] border-2 border-[#fc0000] flex items-center justify-center mb-6"
      >
        <Check size={36} className="text-[#fc0000]" />
      </motion.div>
      <motion.h2
        initial={{ opacity: 0, y: 10 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ delay: 0.25 }}
        className="text-2xl font-black text-[var(--text-primary)] mb-2"
      >
        {isEditMode ? "Listing Updated!" : "Listing Published!"}
      </motion.h2>
      <motion.p
        initial={{ opacity: 0, y: 10 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ delay: 0.35 }}
        className="text-sm text-[var(--text-muted)] mb-8 max-w-xs"
      >
        {isEditMode
          ? "Your listing has been updated successfully."
          : "Your listing is now live. Buyers in your area can find it."}
      </motion.p>
      <motion.button
        initial={{ opacity: 0, y: 10 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ delay: 0.45 }}
        whileHover={{ scale: 1.03 }}
        whileTap={{ scale: 0.97 }}
        onClick={onDashboard}
        className="flex items-center justify-center gap-2 rounded-xl bg-[#fc0000] hover:bg-[#ff4040] px-8 py-3.5 text-sm font-bold text-white transition-all shadow-lg shadow-[rgba(252,0,0,0.25)]"
      >
        View My Listings
      </motion.button>
    </motion.div>
  );
}

/* ─── Wizard Root ───────────────────────────────────────────────────────────── */

interface ListingWizardProps {
  onBack?: () => void;
  onComplete?: () => void;
  editListingId?: number;
  initialData?: Partial<WizardData>;
}

export function ListingWizard({
  onBack,
  onComplete,
  editListingId,
  initialData,
}: ListingWizardProps) {
  const dispatch = useAppDispatch();
  const { categories: apiCategories } = useAppSelector(
    (s) => s.marketplaceCategory,
  );
  const currencyCode = useAppSelector((s) => s.generalSetting.currencyCode);

  const isEditMode = !!editListingId;

  const [step, setStep] = useState(1);
  const [direction, setDirection] = useState(1);
  const [data, setData] = useState<WizardData>(() =>
    initialData ? { ...INITIAL_DATA, ...initialData } : INITIAL_DATA,
  );
  const [isPublishing, setIsPublishing] = useState(false);
  const [isPublished, setIsPublished] = useState(false);
  const [publishError, setPublishError] = useState<string | null>(null);
  const [errors, setErrors] = useState<Record<string, string>>({});

  /* Snapshot of original values — used in edit mode to send only changed fields */
  const originalDataRef = useRef<Partial<WizardData> | undefined>(initialData);

  useEffect(() => {
    dispatch(fetchMarketplaceCategories());
  }, [dispatch]);

  /* Re-init when initialData arrives (edit mode: data comes async) */
  useEffect(() => {
    if (initialData) {
      setData({ ...INITIAL_DATA, ...initialData });
      originalDataRef.current = initialData;
    }
  }, [initialData]);

  const update = useCallback((patch: Partial<WizardData>) => {
    setData((prev) => ({ ...prev, ...patch }));
    /* Clear errors for any fields that just changed */
    setErrors((prev) => {
      if (Object.keys(prev).length === 0) return prev;
      const next = { ...prev };
      Object.keys(patch).forEach((k) => delete next[k]);
      if ("categoryId" in patch) delete next.category;
      if ("condition" in patch) delete next.condition;
      if ("images" in patch) delete next.images;
      if ("city" in patch) delete next.city;
      return next;
    });
  }, []);

  const goTo = (next: number) => {
    setDirection(next > step ? 1 : -1);
    setErrors({});
    setStep(next);
  };

  const handleNext = () => {
    const stepErrors = validateStep(step, data);
    if (Object.keys(stepErrors).length > 0) {
      setErrors(stepErrors);
      return;
    }
    setErrors({});
    if (step < 5) goTo(step + 1);
  };

  const handleBack = () => {
    if (step > 1) {
      goTo(step - 1);
    } else {
      onBack?.();
    }
  };

  const handlePublish = async () => {
    /* Validate all steps before submitting */
    for (let s = 1; s <= 4; s++) {
      const stepErrors = validateStep(s, data);
      if (Object.keys(stepErrors).length > 0) {
        setErrors(stepErrors);
        goTo(s);
        return;
      }
    }

    setIsPublishing(true);
    setPublishError(null);
    try {
      const channelId = Cookies.get("channel_id") ?? "";
      const orig = originalDataRef.current;

      const coverImg = data.images.find((i) => i.isCover) ?? data.images[0];
      const otherImages = data.images.filter((i) => !i.isCover);

      /* 1. Upload only non-cover new images via listing_image_upload */
      const uploadedOthers = await Promise.all(
        otherImages.map(async (img) => {
          if (!img.file) return { ...img, uploadedFilename: undefined };
          const fd = new FormData();
          fd.append("image", img.file);
          const res = await apiClient.post(
            API_ENDPOINTS.CLASSIFIED.UPLOAD_IMAGE,
            fd,
            { headers: { "Content-Type": "multipart/form-data" } },
          );
          /* Response: { result: [{ image: "filename.jpg", image_url: "..." }] } */
          return {
            ...img,
            uploadedFilename: (res.data.result?.[0]?.image ?? "") as string,
          };
        }),
      );

      /* 2. Build listing_content from non-cover images */
      const listingContent = uploadedOthers.map((img) => {
        if (img.uploadedFilename) {
          return { image: img.uploadedFilename };
        }
        return {
          id: String(img.galleryId ?? ""),
          image: "",
          old_image: img.existingFilename ?? "",
          old_storage_type: "1",
        };
      });

      /* 3. Condition API value */
      const conditionValue = data.condition === "new" ? 1 : 2;

      /* 4. Build FormData.
            Edit mode: only send fields that have actually changed (backend skips
            unchanged fields but errors if a file-type field is sent as a string). */
      const form = new FormData();
      const headers = { "Content-Type": "multipart/form-data" };

      /* Always required */
      form.append("channel_id", channelId);
      if (isEditMode) form.append("listing_id", String(editListingId));

      /* Helper: returns true when we should include this field */
      const isDiff = (
        cur: string | number,
        origVal: string | number | undefined,
      ) => !isEditMode || String(cur) !== String(origVal ?? "");

      if (isDiff(data.categoryId, orig?.categoryId))
        form.append("classified_category_id", String(data.categoryId));
      if (isDiff(data.title, orig?.title)) form.append("title", data.title);
      if (isDiff(data.description, orig?.description))
        form.append("description", data.description);
      if (isDiff(data.price, orig?.price)) form.append("price", data.price);
      if (isDiff(data.quantity, orig?.quantity ?? "1"))
        form.append("quantity", data.quantity);
      if (isDiff(conditionValue, orig?.condition === "new" ? 1 : 2))
        form.append("listing_condition", String(conditionValue));
      if (isDiff(data.city, orig?.city)) form.append("city", data.city);
      if (isDiff(data.area, orig?.area)) form.append("area", data.area);
      if (data.latitude && isDiff(data.latitude, orig?.latitude))
        form.append("latitude", data.latitude);
      if (data.longitude && isDiff(data.longitude, orig?.longitude))
        form.append("longitude", data.longitude);

      /* image: only if user selected a new cover file */
      if (coverImg?.file) form.append("image", coverImg.file);

      /* listing_content: for add always include; for edit only if gallery changed */
      const origGalleryKey = (orig?.images ?? [])
        .filter((i) => !i.isCover)
        .map((i) => i.galleryId ?? i.existingFilename)
        .join(",");
      const curGalleryKey = otherImages
        .filter((i) => !i.file) // existing (not new)
        .map((i) => i.galleryId ?? i.existingFilename)
        .join(",");
      const galleryChanged =
        uploadedOthers.some((i) => !!i.file) ||
        origGalleryKey !== curGalleryKey;

      if (!isEditMode || galleryChanged) {
        form.append("listing_content", JSON.stringify(listingContent));
      }

      await apiClient.post(
        isEditMode
          ? API_ENDPOINTS.CLASSIFIED.EDIT_LISTING
          : API_ENDPOINTS.CLASSIFIED.ADD_LISTING,
        form,
        { headers },
      );

      setIsPublished(true);
    } catch (err: unknown) {
      const msg =
        err instanceof Error
          ? err.message
          : "Failed to save listing. Please try again.";
      setPublishError(msg);
    } finally {
      setIsPublishing(false);
    }
  };

  const handleDraft = () => {
    onComplete?.();
  };

  const stepLabels = ["Details", "Photos", "Pricing", "Location", "Review"];

  if (isPublished) {
    return (
      <SuccessScreen
        onDashboard={() => onComplete?.()}
        isEditMode={isEditMode}
      />
    );
  }

  return (
    <div className="flex flex-col min-h-full">
      {/* Top bar */}
      <div className="sticky top-0 z-20 bg-[var(--bg)] border-b border-[var(--border)] px-4 pt-4 pb-3">
        <div className="flex items-center gap-3 mb-4">
          <button
            type="button"
            onClick={handleBack}
            className="w-8 h-8 rounded-lg border border-[var(--border)] bg-[var(--card)] flex items-center justify-center hover:bg-[var(--deep)] transition-colors flex-shrink-0"
          >
            <ArrowLeft size={16} className="text-[var(--text-primary)]" />
          </button>
          <div className="flex-1 min-w-0">
            <h1 className="text-base font-bold text-[var(--text-primary)] truncate">
              {isEditMode ? "Edit Listing" : "Create New Listing"}
            </h1>
            <p className="text-xs text-[var(--text-muted)]">
              Step {step} of 5 — {stepLabels[step - 1]}
            </p>
          </div>
        </div>

        {/* Progress steps */}
        <div className="flex items-center gap-0">
          {STEPS.map((s, i) => (
            <div key={s.id} className="flex items-center flex-1">
              <button
                type="button"
                onClick={() => step > s.id && goTo(s.id)}
                disabled={step <= s.id}
                className="relative flex flex-col items-center gap-1 w-full"
              >
                <div
                  className={`w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all duration-300 ${
                    step > s.id
                      ? "bg-[#fc0000] text-white"
                      : step === s.id
                        ? "bg-[rgba(252,0,0,0.15)] border-2 border-[#fc0000] text-[#fc0000]"
                        : "bg-[var(--card)] border border-[var(--border)] text-[var(--text-muted)]"
                  }`}
                >
                  {step > s.id ? <Check size={12} /> : s.id}
                </div>
                <span
                  className={`text-[9px] font-semibold uppercase tracking-wider hidden sm:block ${
                    step >= s.id
                      ? "text-[var(--text-primary)]"
                      : "text-[var(--text-muted)]"
                  }`}
                >
                  {s.label}
                </span>
              </button>
              {i < STEPS.length - 1 && (
                <div className="flex-1 h-0.5 mx-1 rounded-full overflow-hidden bg-[var(--border)]">
                  <motion.div
                    className="h-full bg-[#fc0000] rounded-full"
                    initial={{ width: "0%" }}
                    animate={{ width: step > s.id ? "100%" : "0%" }}
                    transition={{ duration: 0.4, ease: "easeInOut" }}
                  />
                </div>
              )}
            </div>
          ))}
        </div>
      </div>

      {/* Step content */}
      <div className="flex-1 px-4 py-6 overflow-y-auto">
        <AnimatePresence mode="wait" custom={direction}>
          <motion.div
            key={step}
            custom={direction}
            variants={slideVariants}
            initial="enter"
            animate="center"
            exit="exit"
          >
            {step === 1 && (
              <Step1
                data={data}
                update={update}
                apiCategories={apiCategories}
                errors={errors}
              />
            )}
            {step === 2 && (
              <Step2 data={data} update={update} errors={errors} />
            )}
            {step === 3 && (
              <Step3
                data={data}
                update={update}
                errors={errors}
                currencyCode={currencyCode}
              />
            )}
            {step === 4 && (
              <Step4 data={data} update={update} errors={errors} />
            )}
            {step === 5 && (
              <Step5
                data={data}
                onPublish={handlePublish}
                onDraft={handleDraft}
                isPublishing={isPublishing}
                error={publishError}
                isEditMode={isEditMode}
                currencyCode={currencyCode}
              />
            )}
          </motion.div>
        </AnimatePresence>
      </div>

      {/* Bottom nav (hidden on step 5 which has its own actions) */}
      {step < 5 && (
        <div className="sticky bottom-0 border-t border-[var(--border)] bg-[var(--bg)] px-4 py-3 pb-safe">
          <AnimatePresence>
            {Object.keys(errors).length > 0 && (
              <motion.div
                initial={{ opacity: 0, y: 6 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: 4 }}
                className="flex items-center gap-2 mb-2.5 px-1"
              >
                <AlertCircle size={13} className="text-[#fc0000] shrink-0" />
                <p className="text-xs text-[#fc0000]">
                  Fix {Object.keys(errors).length} error
                  {Object.keys(errors).length > 1 ? "s" : ""} above to continue
                </p>
              </motion.div>
            )}
          </AnimatePresence>
          <motion.button
            type="button"
            whileHover={{ scale: 1.02 }}
            whileTap={{ scale: 0.97 }}
            onClick={handleNext}
            className="w-full flex items-center justify-center gap-2 rounded-xl bg-[#fc0000] hover:bg-[#ff4040] active:bg-[#c80000] px-5 py-3.5 text-sm font-bold text-white transition-all shadow-lg shadow-[rgba(252,0,0,0.2)]"
          >
            Continue
            <ArrowRight size={16} />
          </motion.button>
        </div>
      )}
    </div>
  );
}
