import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "wouter";
import {
  useListServices,
  getListServicesQueryKey,
  useReorderServices,
} from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
import { ArrowRight, ArrowLeft, Coffee, ImageIcon, Inbox } from "lucide-react";
import {
  DndContext,
  PointerSensor,
  TouchSensor,
  useSensor,
  useSensors,
  type DragEndEvent,
  type DragStartEvent,
} from "@dnd-kit/core";
import {
  SortableContext,
  rectSortingStrategy,
  useSortable,
  arrayMove,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/contexts/AuthContext";
import { resolveServiceImageUrl } from "@/lib/image-url";
import { OrderServiceModal } from "@/components/order-service-modal";
import { AppDock } from "@/components/app-dock";

type CatalogService = {
  id: number;
  nameAr: string;
  nameEn: string;
  imageUrl?: string | null;
};

type ServiceRow = {
  id: number;
  nameAr: string;
  nameEn: string;
  imageUrl?: string | null;
  isAvailable: boolean;
};

function ServiceCardImage({ src, alt }: { src: string | null; alt: string }) {
  const [errored, setErrored] = useState(false);
  const showImage = src && !errored;
  return (
    <div className="w-full aspect-square bg-slate-100 overflow-hidden flex items-center justify-center">
      {showImage ? (
        <img
          src={src}
          alt={alt}
          className="w-full h-full object-cover"
          onError={() => setErrored(true)}
          draggable={false}
        />
      ) : (
        <ImageIcon size={24} className="text-slate-300" />
      )}
    </div>
  );
}

type SortableServiceCardProps = {
  service: ServiceRow;
  lang: string;
  editMode: boolean;
  isDragging: boolean;
  canEnterEditMode: boolean;
  onTapInView: () => void;
  onLongPress: () => void;
  unavailableLabel: string;
};

function SortableServiceCard({
  service,
  lang,
  editMode,
  isDragging,
  canEnterEditMode,
  onTapInView,
  onLongPress,
  unavailableLabel,
}: SortableServiceCardProps) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
    isDragging: thisDragging,
  } = useSortable({ id: service.id, disabled: !editMode });

  const name = lang === "ar" ? service.nameAr : service.nameEn;
  const resolvedImage = resolveServiceImageUrl(service.imageUrl);

  // Long-press detection — only fires when NOT already in edit mode.
  // We track pointer position so a scroll/drag of more than 10 px
  // cancels the timer (matches iOS behaviour and prevents accidental
  // entry during fast scrolling).
  const pressTimer = useRef<number | null>(null);
  const pressStart = useRef<{ x: number; y: number } | null>(null);
  const longPressed = useRef(false);

  const clearTimer = () => {
    if (pressTimer.current !== null) {
      window.clearTimeout(pressTimer.current);
      pressTimer.current = null;
    }
  };

  const handlePointerDown = (e: React.PointerEvent<HTMLButtonElement>) => {
    if (editMode || !canEnterEditMode) return;
    longPressed.current = false;
    pressStart.current = { x: e.clientX, y: e.clientY };
    pressTimer.current = window.setTimeout(() => {
      longPressed.current = true;
      pressTimer.current = null;
      try {
        navigator.vibrate?.(10);
      } catch {
        /* not supported — ignore */
      }
      onLongPress();
    }, 500);
  };

  const handlePointerMove = (e: React.PointerEvent<HTMLButtonElement>) => {
    if (editMode || pressStart.current === null) return;
    const dx = e.clientX - pressStart.current.x;
    const dy = e.clientY - pressStart.current.y;
    if (dx * dx + dy * dy > 100) clearTimer();
  };

  const handlePointerEnd = () => {
    clearTimer();
    pressStart.current = null;
  };

  const handleClick = (e: React.MouseEvent) => {
    // Swallow the click that follows a long-press so the order modal
    // doesn't open the instant we enter edit mode.
    if (longPressed.current) {
      longPressed.current = false;
      e.preventDefault();
      e.stopPropagation();
      return;
    }
    if (editMode) {
      e.preventDefault();
      return;
    }
    onTapInView();
  };

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    zIndex: thisDragging ? 20 : undefined,
  } as React.CSSProperties;

  // Jiggle every card while editing, except the one being actively
  // dragged (its own motion already conveys intent and the rotation
  // would fight the drag transform).
  const jiggleClass = editMode && !thisDragging ? "animate-services-jiggle" : "";

  return (
    <button
      ref={setNodeRef}
      type="button"
      onClick={handleClick}
      onPointerDown={handlePointerDown}
      onPointerMove={handlePointerMove}
      onPointerUp={handlePointerEnd}
      onPointerCancel={handlePointerEnd}
      onPointerLeave={handlePointerEnd}
      disabled={!editMode && !service.isAvailable}
      aria-label={name}
      style={style}
      {...(editMode ? listeners : {})}
      {...(editMode ? attributes : {})}
      className={`glass-panel rounded-lg overflow-hidden flex flex-col card-hover text-start select-none disabled:opacity-60 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${editMode ? "touch-none" : ""} ${jiggleClass} ${isDragging ? "opacity-90" : ""}`}
    >
      <ServiceCardImage src={resolvedImage} alt={name} />
      <div className="p-2 flex flex-col gap-0.5 flex-1">
        <div className="flex items-start justify-between gap-1">
          <h3 className="text-xs font-semibold text-foreground leading-tight truncate">
            {name}
          </h3>
          {!service.isAvailable && (
            <Badge variant="secondary" className="text-[9px] px-1 py-0 shrink-0">
              {unavailableLabel}
            </Badge>
          )}
        </div>
      </div>
    </button>
  );
}

export default function ServicesPage() {
  const { t, i18n } = useTranslation();
  const [, setLocation] = useLocation();
  const { user } = useAuth();
  const { toast } = useToast();
  const queryClient = useQueryClient();
  const lang = i18n.language;
  const isRtl = lang === "ar";
  const isAdmin = user?.roles?.includes("admin") ?? false;

  const { data: services, isLoading } = useListServices({
    query: { queryKey: getListServicesQueryKey() },
  });

  const reorderMutation = useReorderServices();

  const [orderTarget, setOrderTarget] = useState<CatalogService | null>(null);
  const [editMode, setEditMode] = useState(false);
  // Local working copy of the list — diverges from the server data
  // only while the user is actively reordering, so query refetches
  // don't clobber an in-progress drag session.
  const [orderedIds, setOrderedIds] = useState<number[] | null>(null);
  const [activeDragId, setActiveDragId] = useState<number | null>(null);

  // Sync local order from server data while NOT editing. Once the
  // user enters edit mode we hold the order locally until they tap
  // Done; the mutation's invalidation then refills from the server.
  useEffect(() => {
    if (!editMode && services) {
      setOrderedIds(services.map((s) => s.id));
    }
  }, [services, editMode]);

  const orderedRows: ServiceRow[] = useMemo(() => {
    if (!services) return [];
    const byId = new Map(services.map((s) => [s.id, s]));
    const ids = orderedIds ?? services.map((s) => s.id);
    const rows: ServiceRow[] = [];
    for (const id of ids) {
      const s = byId.get(id);
      if (s) rows.push(s);
    }
    // Append any services that arrived mid-edit (admin added one in
    // another tab) so the grid never loses cards on refetch.
    for (const s of services) {
      if (!ids.includes(s.id)) rows.push(s);
    }
    return rows;
  }, [services, orderedIds]);

  // Drag activation is tuned per-mode:
  //  * Outside edit mode we never want a drag (cards are only tappable
  //    to place an order), so a slightly higher distance threshold
  //    keeps the long-press-to-enter-edit gesture clean.
  //  * Inside edit mode the user has already opted into reordering, so
  //    drags should start instantly with no touch delay — matching the
  //    iOS home-screen "jiggle" feel. A tiny `distance: 2` activation
  //    still tolerates the natural finger jitter that happens before
  //    intentional movement, without making drags feel sluggish.
  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: editMode ? 2 : 6 },
    }),
    useSensor(TouchSensor, {
      activationConstraint: editMode
        ? { distance: 2 }
        : { delay: 150, tolerance: 8 },
    }),
  );

  const BackIcon = isRtl ? ArrowRight : ArrowLeft;

  // Prevents two concurrent exits (e.g. the floating Done button and
  // the document-level outside-tap handler both firing for the same
  // gesture). Without this we'd see double POSTs to /services/reorder.
  const exitingRef = useRef(false);

  const exitEditMode = async () => {
    if (!editMode || exitingRef.current) return;
    exitingRef.current = true;
    setActiveDragId(null);

    const serverIds = (services ?? []).map((s) => s.id);
    const localIds = orderedIds ?? serverIds;
    const changed =
      localIds.length !== serverIds.length ||
      localIds.some((id, i) => id !== serverIds[i]);

    try {
      if (!changed) {
        setEditMode(false);
        return;
      }
      if (!isAdmin) {
        // Non-admins can enter edit mode for the haptic affordance but
        // can't persist — silently revert.
        setOrderedIds(serverIds);
        setEditMode(false);
        return;
      }

      try {
        await reorderMutation.mutateAsync({ data: { orderedIds: localIds } });
        await queryClient.invalidateQueries({
          queryKey: getListServicesQueryKey(),
        });
        // Only drop edit mode after the refetch resolves so the
        // sync-from-server effect doesn't briefly rehydrate from the
        // stale pre-mutation cache and snap the order back.
        setEditMode(false);
      } catch (err) {
        toast({
          title: t("services.editMode.saveFailed"),
          variant: "destructive",
        });
        setOrderedIds(serverIds);
        setEditMode(false);
      }
    } finally {
      exitingRef.current = false;
    }
  };

  const onLongPress = () => {
    // Reordering is admin-only — silently ignore long-press for everyone
    // else so non-admins never see a jiggle UI they can't persist.
    if (!isAdmin) return;
    if (!editMode) setEditMode(true);
  };

  const onDragStart = (e: DragStartEvent) => {
    setActiveDragId(Number(e.active.id));
  };

  const onDragEnd = (e: DragEndEvent) => {
    setActiveDragId(null);
    const { active, over } = e;
    if (!over || active.id === over.id) return;
    setOrderedIds((prev) => {
      const base = prev ?? (services ?? []).map((s) => s.id);
      const from = base.indexOf(Number(active.id));
      const to = base.indexOf(Number(over.id));
      if (from < 0 || to < 0) return base;
      return arrayMove(base, from, to);
    });
  };

  // Tap outside the grid (on the empty page area) exits edit mode.
  const gridRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    if (!editMode) return;
    const onDocPointerDown = (ev: PointerEvent) => {
      const target = ev.target as Node | null;
      if (target && gridRef.current && !gridRef.current.contains(target)) {
        // Defer so the click on the Done button (which sits outside
        // the grid) still gets to fire its own handler first.
        setTimeout(() => exitEditMode(), 0);
      }
    };
    document.addEventListener("pointerdown", onDocPointerDown);
    return () => document.removeEventListener("pointerdown", onDocPointerDown);
    // exitEditMode closes over editMode/orderedIds — re-bind each render.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [editMode, orderedIds]);

  return (
    <div className="min-h-screen os-bg flex flex-col">
      {/* Inline keyframes — no Tailwind plugin available for arbitrary
          keyframes and we'd rather not pull tailwindcss-animate just
          for this one effect. Kept here so the page is self-contained. */}
      <style>{`
        @keyframes services-jiggle {
          0% { transform: rotate(-1.2deg); }
          50% { transform: rotate(1.2deg); }
          100% { transform: rotate(-1.2deg); }
        }
        .animate-services-jiggle { animation: services-jiggle 0.25s ease-in-out infinite; transform-origin: center; }
        @media (prefers-reduced-motion: reduce) {
          .animate-services-jiggle { animation: none; }
        }
      `}</style>

      {/* Header */}
      <div className="glass-panel border-b border-slate-200/70 px-4 py-4 flex items-center gap-3 sticky top-0 z-10">
        <button
          onClick={() => setLocation("/")}
          className="p-2 rounded-xl hover:bg-slate-100 text-muted-foreground hover:text-foreground transition-colors"
        >
          <BackIcon size={20} />
        </button>
        <div className="flex items-center gap-2">
          <Coffee size={20} className="text-amber-400" />
          <h1 className="text-lg font-semibold text-foreground">
            {t("services.title")}
          </h1>
        </div>
        <div className="ms-auto">
          <Button
            variant="ghost"
            size="sm"
            className="gap-1.5"
            onClick={() => setLocation("/my-orders")}
          >
            <Inbox size={16} />
            <span className="hidden sm:inline">
              {t("services.myOrdersLink")}
            </span>
          </Button>
        </div>
      </div>

      <div className="flex-1 p-4 pb-8">
        {isLoading ? (
          <div className="flex items-center justify-center py-20 text-muted-foreground">
            {t("common.loading")}
          </div>
        ) : !services?.length ? (
          <div className="flex items-center justify-center py-20 text-muted-foreground">
            {t("services.noServices")}
          </div>
        ) : (
          <>
            {editMode && (
              <div className="text-center text-xs text-muted-foreground mb-3">
                {t("services.editMode.hint")}
              </div>
            )}
            <DndContext
              sensors={sensors}
              onDragStart={onDragStart}
              onDragEnd={onDragEnd}
            >
              <SortableContext
                items={orderedRows.map((s) => s.id)}
                strategy={rectSortingStrategy}
              >
                <div
                  ref={gridRef}
                  className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-7 xl:grid-cols-8 gap-2"
                >
                  {orderedRows.map((service) => (
                    <SortableServiceCard
                      key={service.id}
                      service={service}
                      lang={lang}
                      editMode={editMode}
                      isDragging={activeDragId === service.id}
                      canEnterEditMode={isAdmin}
                      onTapInView={() => {
                        if (!service.isAvailable) return;
                        setOrderTarget({
                          id: service.id,
                          nameAr: service.nameAr,
                          nameEn: service.nameEn,
                          imageUrl: service.imageUrl,
                        });
                      }}
                      onLongPress={onLongPress}
                      unavailableLabel={t("services.unavailable")}
                    />
                  ))}
                </div>
              </SortableContext>
            </DndContext>
          </>
        )}
      </div>

      {editMode && (
        <div className="fixed bottom-24 inset-x-0 flex justify-center z-40 pointer-events-none">
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              exitEditMode();
            }}
            className="pointer-events-auto glass-panel rounded-full px-6 py-2 text-sm font-semibold text-foreground shadow-lg hover:bg-slate-100"
            disabled={reorderMutation.isPending}
          >
            {reorderMutation.isPending
              ? t("common.loading")
              : t("services.editMode.done")}
          </button>
        </div>
      )}

      <OrderServiceModal
        open={orderTarget !== null}
        onOpenChange={(o) => {
          if (!o) setOrderTarget(null);
        }}
        service={orderTarget}
        resolveImage={resolveServiceImageUrl}
      />
      <AppDock currentSlug="services" />
    </div>
  );
}
