"use client";

import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
import {
  DndContext,
  PointerSensor,
  TouchSensor,
  KeyboardSensor,
  closestCenter,
  useSensor,
  useSensors,
  type DragEndEvent,
} from "@dnd-kit/core";
import {
  SortableContext,
  arrayMove,
  rectSortingStrategy,
  useSortable,
  sortableKeyboardCoordinates,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

export type MediaItem = {
  id: string;
  type: "PHOTO" | "VIDEO";
  s3Url: string;
  s3Key: string;
  sortOrder: number;
};

type Props = {
  carbetId: string;
  initialMedia: MediaItem[];
};

type UploadEntry = {
  tempId: string;
  name: string;
  sizeBytes: number;
  mime: string;
  progress: number;
  error?: string;
  done: boolean;
};

const MAX_PARALLEL = 3;

export function MediaUploader({ carbetId, initialMedia }: Props) {
  const [items, setItems] = useState<MediaItem[]>(
    [...initialMedia].sort((a, b) => a.sortOrder - b.sortOrder),
  );
  const [uploads, setUploads] = useState<UploadEntry[]>([]);
  const [dragging, setDragging] = useState(false);
  const inputId = useId();
  const fileInput = useRef<HTMLInputElement>(null);
  const queueRef = useRef<File[]>([]);
  const activeRef = useRef(0);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 6 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  const allIds = useMemo(() => items.map((i) => i.id), [items]);

  const reorderOnServer = useCallback(
    async (orderedIds: string[]) => {
      await fetch("/api/media/reorder", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ carbetId, orderedIds }),
      }).catch(() => {});
    },
    [carbetId],
  );

  function onDragEnd(e: DragEndEvent) {
    const { active, over } = e;
    if (!over || active.id === over.id) return;
    setItems((prev) => {
      const oldIdx = prev.findIndex((p) => p.id === active.id);
      const newIdx = prev.findIndex((p) => p.id === over.id);
      if (oldIdx < 0 || newIdx < 0) return prev;
      const next = arrayMove(prev, oldIdx, newIdx);
      reorderOnServer(next.map((m) => m.id));
      return next;
    });
  }

  const setCover = useCallback(
    (id: string) => {
      setItems((prev) => {
        const idx = prev.findIndex((p) => p.id === id);
        if (idx <= 0) return prev;
        const next = arrayMove(prev, idx, 0);
        reorderOnServer(next.map((m) => m.id));
        return next;
      });
    },
    [reorderOnServer],
  );

  const removeItem = useCallback(async (id: string) => {
    if (!confirm("Supprimer ce média ?")) return;
    const res = await fetch(`/api/media/${id}`, { method: "DELETE" });
    if (res.ok) setItems((prev) => prev.filter((p) => p.id !== id));
  }, []);

  const processFile = useCallback(async function processFile(file: File): Promise<void> {
    const tempId = crypto.randomUUID();
    setUploads((u) => [
      ...u,
      { tempId, name: file.name, sizeBytes: file.size, mime: file.type, progress: 0, done: false },
    ]);
    try {
      const presignRes = await fetch("/api/uploads/presign", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ carbetId, mime: file.type, sizeBytes: file.size }),
      });
      const presign = await presignRes.json();
      if (!presignRes.ok) throw new Error(presign?.error || "presign refusé");

      await new Promise<void>((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.upload.addEventListener("progress", (ev) => {
          if (!ev.lengthComputable) return;
          const pct = Math.round((ev.loaded / ev.total) * 100);
          setUploads((u) => u.map((x) => (x.tempId === tempId ? { ...x, progress: pct } : x)));
        });
        xhr.addEventListener("load", () =>
          xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new Error(`HTTP ${xhr.status}`)),
        );
        xhr.addEventListener("error", () => reject(new Error("Réseau coupé")));
        xhr.open("PUT", presign.uploadUrl);
        xhr.setRequestHeader("Content-Type", file.type);
        xhr.send(file);
      });

      const finalizeRes = await fetch("/api/uploads/finalize", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          carbetId,
          s3Key: presign.s3Key,
          s3Url: presign.publicUrl,
          mime: file.type,
        }),
      });
      const finalize = await finalizeRes.json();
      if (!finalizeRes.ok) throw new Error(finalize?.error || "finalize refusé");
      setItems((prev) => [...prev, finalize.media]);
      setUploads((u) => u.map((x) => (x.tempId === tempId ? { ...x, progress: 100, done: true } : x)));
      // Cleanup après 2s
      setTimeout(() => {
        setUploads((u) => u.filter((x) => x.tempId !== tempId));
      }, 2000);
    } catch (e) {
      const msg = e instanceof Error ? e.message : String(e);
      setUploads((u) => u.map((x) => (x.tempId === tempId ? { ...x, error: msg } : x)));
    }
  }, [carbetId]);

  const popQueueRef = useRef<() => void>(() => {});
  const popQueue = useCallback(() => {
    while (activeRef.current < MAX_PARALLEL && queueRef.current.length > 0) {
      const file = queueRef.current.shift()!;
      activeRef.current++;
      processFile(file).finally(() => {
        activeRef.current--;
        popQueueRef.current();
      });
    }
  }, [processFile]);
  useEffect(() => {
    popQueueRef.current = popQueue;
  }, [popQueue]);

  function addFiles(files: FileList | File[]) {
    const arr = Array.from(files);
    queueRef.current.push(...arr);
    popQueue();
  }

  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    if (e.target.files) addFiles(e.target.files);
    if (fileInput.current) fileInput.current.value = "";
  }

  function onDrop(e: React.DragEvent<HTMLDivElement>) {
    e.preventDefault();
    setDragging(false);
    if (e.dataTransfer.files) addFiles(e.dataTransfer.files);
  }

  // Permet le coller depuis presse-papier
  useEffect(() => {
    function onPaste(e: ClipboardEvent) {
      if (!e.clipboardData?.files?.length) return;
      addFiles(e.clipboardData.files);
    }
    window.addEventListener("paste", onPaste);
    return () => window.removeEventListener("paste", onPaste);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div className="space-y-3">
      <div
        onDragOver={(e) => {
          e.preventDefault();
          setDragging(true);
        }}
        onDragLeave={() => setDragging(false)}
        onDrop={onDrop}
        className={
          "rounded-lg border-2 border-dashed p-4 text-center transition " +
          (dragging
            ? "border-emerald-500 bg-emerald-50"
            : "border-zinc-300 bg-zinc-50 hover:border-zinc-400")
        }
      >
        <label htmlFor={inputId} className="block cursor-pointer">
          <div className="text-sm text-zinc-700">
            <strong>Déposez vos photos ou vidéos</strong> ici, ou cliquez pour parcourir
          </div>
          <div className="mt-1 text-xs text-zinc-500">
            JPG / PNG / WebP / AVIF (max 10 Mo) · MP4 / MOV / WebM (max 200 Mo) · plusieurs fichiers OK
          </div>
        </label>
        <input
          id={inputId}
          ref={fileInput}
          type="file"
          accept="image/jpeg,image/png,image/webp,image/avif,video/mp4,video/quicktime,video/webm"
          multiple
          capture="environment"
          onChange={onChange}
          className="sr-only"
        />
      </div>

      {uploads.length > 0 ? (
        <ul className="space-y-1.5">
          {uploads.map((u) => (
            <li key={u.tempId} className="rounded border border-zinc-200 bg-white px-3 py-2 text-xs">
              <div className="flex items-center justify-between">
                <span className="truncate font-medium text-zinc-700">{u.name}</span>
                <span className="ml-2 text-zinc-500">
                  {u.error
                    ? "❌"
                    : u.done
                      ? "✓"
                      : `${Math.round(u.sizeBytes / 1000)} ko · ${u.progress}%`}
                </span>
              </div>
              <div className="mt-1 h-1 overflow-hidden rounded-full bg-zinc-200">
                <div
                  className={
                    "h-full transition-all " +
                    (u.error ? "bg-rose-500" : u.done ? "bg-emerald-500" : "bg-emerald-600")
                  }
                  style={{ width: `${u.progress}%` }}
                />
              </div>
              {u.error ? <div className="mt-1 text-rose-700">{u.error}</div> : null}
            </li>
          ))}
        </ul>
      ) : null}

      {items.length > 0 ? (
        <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
          <SortableContext items={allIds} strategy={rectSortingStrategy}>
            <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4">
              {items.map((item, idx) => (
                <SortableTile
                  key={item.id}
                  item={item}
                  isCover={idx === 0}
                  onSetCover={() => setCover(item.id)}
                  onDelete={() => removeItem(item.id)}
                />
              ))}
            </div>
          </SortableContext>
        </DndContext>
      ) : (
        <p className="rounded border border-dashed border-zinc-200 px-3 py-6 text-center text-xs text-zinc-500">
          Pas encore de média. Ajoutez votre premier ci-dessus.
        </p>
      )}

      <p className="text-[11px] text-zinc-500">
        Glissez-déposez pour réordonner · Étoile = cover (image principale sur le catalogue)
      </p>
    </div>
  );
}

function SortableTile({
  item,
  isCover,
  onSetCover,
  onDelete,
}: {
  item: MediaItem;
  isCover: boolean;
  onSetCover: () => void;
  onDelete: () => void;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: item.id,
  });
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.5 : 1,
  };
  return (
    <div
      ref={setNodeRef}
      style={style}
      className={
        "group relative overflow-hidden rounded-md border bg-zinc-100 " +
        (isCover ? "border-emerald-500 ring-2 ring-emerald-300" : "border-zinc-200")
      }
    >
      <div {...attributes} {...listeners} className="aspect-square w-full cursor-grab touch-none active:cursor-grabbing">
        {item.type === "VIDEO" ? (
          <video
            src={item.s3Url}
            preload="metadata"
            muted
            playsInline
            className="h-full w-full bg-black object-cover"
          />
        ) : (
          // eslint-disable-next-line @next/next/no-img-element
          <img
            src={item.s3Url}
            alt=""
            loading="lazy"
            draggable={false}
            className="h-full w-full object-cover"
          />
        )}
      </div>
      {isCover ? (
        <span className="absolute left-1 top-1 rounded bg-emerald-600 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-white">
          Cover
        </span>
      ) : null}
      <span className="pointer-events-none absolute right-1 top-1 rounded bg-black/60 px-1 py-0.5 text-[9px] font-semibold uppercase tracking-wider text-white">
        {item.type}
      </span>
      <div className="absolute inset-x-0 bottom-0 flex justify-end gap-1 bg-gradient-to-t from-black/70 to-transparent p-1.5 opacity-0 transition group-hover:opacity-100 group-focus-within:opacity-100">
        {!isCover ? (
          <button
            type="button"
            onClick={onSetCover}
            className="rounded bg-white/90 px-2 py-0.5 text-[10px] font-semibold text-zinc-900 hover:bg-white"
            title="Définir comme cover"
          >
            ★ Cover
          </button>
        ) : null}
        <button
          type="button"
          onClick={onDelete}
          className="rounded bg-rose-600 px-2 py-0.5 text-[10px] font-semibold text-white hover:bg-rose-700"
          title="Supprimer"
        >
          ✕
        </button>
      </div>
    </div>
  );
}
