import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Crop, Pause, Play, Scissors, Loader2 } from "lucide-react";
import clsx from "clsx";
import Modal from "./Modal";
import { notify } from "../lib/notifications";
import { api, type DownloadJob, type EditSpec } from "../lib/api";

// mm:ss(.d) — a compact timecode for the trim readout.
function tc(s: number): string {
  if (!isFinite(s) || s < 0) s = 0;
  const m = Math.floor(s / 60);
  const sec = s - m * 60;
  return `${m}:${sec.toFixed(1).padStart(4, "0")}`;
}

type Frac = { x: number; y: number; w: number; h: number }; // crop as 0..1 fractions of the frame

const btn =
  "px-3 py-1.5 rounded-lg text-sm font-medium transition disabled:opacity-50";
const inputCls =
  "w-full rounded-lg bg-surface border border-border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/40";

export default function VideoEditor({
  job,
  onClose,
}: {
  job: DownloadJob;
  onClose: () => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const videoRef = useRef<HTMLVideoElement>(null);
  const trackRef = useRef<HTMLDivElement>(null);

  const srcDur = job.asset?.duration_s ?? 0;
  const [duration, setDuration] = useState(srcDur);
  const [current, setCurrent] = useState(0);
  const [playing, setPlaying] = useState(false);

  const [start, setStart] = useState(0);
  const [end, setEnd] = useState(srcDur);
  const [cropOn, setCropOn] = useState(false);
  const [crop, setCrop] = useState<Frac>({ x: 0.1, y: 0.1, w: 0.8, h: 0.8 });
  const [accurate, setAccurate] = useState(true);
  const [split, setSplit] = useState(1);
  const [name, setName] = useState("");

  const fileUrl = useMemo(() => api.downloadFileUrl(job.id), [job.id]);
  const sb = useQuery({
    queryKey: ["storyboard", job.id],
    queryFn: () => api.downloadStoryboard(job.id),
    staleTime: Infinity,
    retry: false,
  });

  // Keep end pinned to the real duration until the user narrows it.
  const onLoaded = () => {
    const v = videoRef.current;
    if (!v) return;
    const d = v.duration && isFinite(v.duration) ? v.duration : srcDur;
    setDuration(d);
    setEnd((e) => (e <= 0 || e > d ? d : e));
  };

  // Pause at the out-point when previewing the selection.
  const onTimeUpdate = () => {
    const v = videoRef.current;
    if (!v) return;
    setCurrent(v.currentTime);
    if (playing && v.currentTime >= end) {
      v.pause();
      v.currentTime = end;
    }
  };

  const seek = (s: number) => {
    const v = videoRef.current;
    if (v) v.currentTime = Math.max(0, Math.min(duration, s));
  };

  const playPause = () => {
    const v = videoRef.current;
    if (!v) return;
    if (v.paused) {
      if (v.currentTime < start || v.currentTime >= end) v.currentTime = start;
      v.play();
    } else {
      v.pause();
    }
  };

  // --- timeline drag (in/out handles + scrub) ---
  const timeAt = useCallback(
    (clientX: number) => {
      const el = trackRef.current;
      if (!el) return 0;
      const r = el.getBoundingClientRect();
      return Math.max(0, Math.min(1, (clientX - r.left) / r.width)) * duration;
    },
    [duration]
  );

  const [drag, setDrag] = useState<null | "start" | "end" | "seek">(null);
  const onTrackDown = (e: React.PointerEvent, which: "start" | "end" | "seek") => {
    e.preventDefault();
    e.stopPropagation();
    (e.target as Element).setPointerCapture?.(e.pointerId);
    setDrag(which);
    if (which === "seek") seek(timeAt(e.clientX));
  };
  useEffect(() => {
    if (!drag) return;
    const move = (e: PointerEvent) => {
      const tt = timeAt(e.clientX);
      if (drag === "start") setStart(Math.min(tt, end - 0.1));
      else if (drag === "end") setEnd(Math.max(tt, start + 0.1));
      else {
        setCurrent(tt);
        seek(tt);
      }
    };
    const up = () => setDrag(null);
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
    return () => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
    };
  }, [drag, timeAt, start, end]);

  // --- crop drag (move body + resize corner), all in 0..1 frame fractions ---
  const overlayRef = useRef<HTMLDivElement>(null);
  const cropDrag = useRef<null | {
    mode: "move" | "resize";
    px: number;
    py: number;
    orig: Frac;
  }>(null);
  const onCropDown = (e: React.PointerEvent, mode: "move" | "resize") => {
    e.preventDefault();
    e.stopPropagation();
    (e.target as Element).setPointerCapture?.(e.pointerId);
    cropDrag.current = { mode, px: e.clientX, py: e.clientY, orig: { ...crop } };
  };
  useEffect(() => {
    const move = (e: PointerEvent) => {
      const d = cropDrag.current;
      const el = overlayRef.current;
      if (!d || !el) return;
      const r = el.getBoundingClientRect();
      const dx = (e.clientX - d.px) / r.width;
      const dy = (e.clientY - d.py) / r.height;
      if (d.mode === "move") {
        setCrop({
          ...d.orig,
          x: Math.max(0, Math.min(1 - d.orig.w, d.orig.x + dx)),
          y: Math.max(0, Math.min(1 - d.orig.h, d.orig.y + dy)),
        });
      } else {
        setCrop({
          ...d.orig,
          w: Math.max(0.05, Math.min(1 - d.orig.x, d.orig.w + dx)),
          h: Math.max(0.05, Math.min(1 - d.orig.y, d.orig.h + dy)),
        });
      }
    };
    const up = () => (cropDrag.current = null);
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
    return () => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
    };
  }, []);

  // --- create ---
  const pct = (v: number) => `${(duration ? (v / duration) * 100 : 0).toFixed(3)}%`;
  const selDur = Math.max(0, end - start);
  const reencode = cropOn || accurate;

  const cropPx = (): { x: number; y: number; w: number; h: number } | undefined => {
    const v = videoRef.current;
    const vw = v?.videoWidth || job.asset?.width || 0;
    const vh = v?.videoHeight || job.asset?.height || 0;
    if (!cropOn || !vw || !vh) return undefined;
    // Force even dimensions — libx264 requires them.
    const even = (n: number) => Math.max(2, Math.round(n / 2) * 2);
    return {
      x: even(crop.x * vw),
      y: even(crop.y * vh),
      w: even(crop.w * vw),
      h: even(crop.h * vh),
    };
  };

  const create = useMutation({
    mutationFn: async () => {
      const n = Math.max(1, Math.min(20, Math.round(split)));
      const cp = cropPx();
      const seg = selDur / n;
      const base = name.trim();
      for (let i = 0; i < n; i++) {
        const s = start + seg * i;
        const e = i === n - 1 ? end : s + seg;
        const spec: EditSpec = { trim: { start_s: +s.toFixed(3), end_s: +e.toFixed(3) } };
        if (cp) spec.crop = cp;
        else spec.accurate = accurate;
        const dn = n > 1 ? `${base || defaultName} (${i + 1}/${n})` : base || undefined;
        await api.enqueueEdit({ source_job_id: job.id, edit_spec: spec, display_name: dn });
      }
      return n;
    },
    onSuccess: (n) => {
      qc.invalidateQueries({ queryKey: ["downloads"] });
      qc.invalidateQueries({ queryKey: ["download-usage"] });
      notify({ level: "success", message: t("editor.created", { count: n }) });
      onClose();
    },
    onError: () => notify({ level: "error", message: t("editor.failed") }),
  });

  const defaultName = job.display_name || job.asset?.title || "";
  const valid = selDur >= 0.1 && (cropOn || start > 0 || end < duration - 0.01 || split > 1);

  // filmstrip cell backgrounds via percentage sprite positioning (no pixel math needed).
  const strip = sb.data?.available ? sb.data : null;

  return (
    <Modal title={t("editor.title", { name: defaultName })} onClose={onClose} maxWidth="max-w-3xl">
      <div className="space-y-3">
        {/* video + crop overlay */}
        <div className="relative bg-black rounded-lg overflow-hidden select-none">
          <video
            ref={videoRef}
            src={fileUrl}
            onLoadedMetadata={onLoaded}
            onTimeUpdate={onTimeUpdate}
            onPlay={() => setPlaying(true)}
            onPause={() => setPlaying(false)}
            className="w-full max-h-[46vh] mx-auto block"
            playsInline
          />
          {cropOn && (
            <div ref={overlayRef} className="absolute inset-0">
              {/* the box's 9999px boxShadow dims everything outside the crop rectangle */}
              <div
                className="absolute border-2 border-accent cursor-move bg-transparent"
                style={{
                  left: `${crop.x * 100}%`,
                  top: `${crop.y * 100}%`,
                  width: `${crop.w * 100}%`,
                  height: `${crop.h * 100}%`,
                  boxShadow: "0 0 0 9999px rgba(0,0,0,0.5)",
                }}
                onPointerDown={(e) => onCropDown(e, "move")}
              >
                <div
                  className="absolute -right-1.5 -bottom-1.5 w-4 h-4 rounded-sm bg-accent cursor-nwse-resize"
                  onPointerDown={(e) => onCropDown(e, "resize")}
                />
              </div>
            </div>
          )}
        </div>

        {/* transport */}
        <div className="flex items-center gap-3">
          <button onClick={playPause} className="p-2 rounded-lg bg-surface hover:bg-card transition" title={t("editor.playPause")}>
            {playing ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
          </button>
          <div className="text-xs text-muted tabular-nums">
            {tc(current)} / {tc(duration)}
          </div>
          <div className="flex-1" />
          <button onClick={() => setStart(Math.min(current, end - 0.1))} className={clsx(btn, "bg-surface hover:bg-card")}>
            {t("editor.setIn")}
          </button>
          <button onClick={() => setEnd(Math.max(current, start + 0.1))} className={clsx(btn, "bg-surface hover:bg-card")}>
            {t("editor.setOut")}
          </button>
        </div>

        {/* timeline: filmstrip + selection + handles + playhead */}
        <div
          ref={trackRef}
          className="relative h-12 rounded-lg overflow-hidden bg-surface border border-border cursor-pointer"
          onPointerDown={(e) => onTrackDown(e, "seek")}
        >
          {strip && (
            <div className="absolute inset-0 flex">
              {Array.from({ length: strip.count ?? 0 }).map((_, i) => {
                const cols = strip.cols ?? 12;
                const rows = strip.rows ?? 1;
                const col = i % cols;
                const row = Math.floor(i / cols);
                return (
                  <div
                    key={i}
                    className="h-full flex-1"
                    style={{
                      backgroundImage: `url(${api.storyboardImageUrl(job.id)})`,
                      backgroundSize: `${cols * 100}% ${rows * 100}%`,
                      backgroundPosition: `${cols > 1 ? (col / (cols - 1)) * 100 : 0}% ${rows > 1 ? (row / (rows - 1)) * 100 : 0}%`,
                    }}
                  />
                );
              })}
            </div>
          )}
          {/* dim outside selection */}
          <div className="absolute inset-y-0 left-0 bg-black/60" style={{ width: pct(start) }} />
          <div className="absolute inset-y-0 right-0 bg-black/60" style={{ left: pct(end) }} />
          {/* selection border */}
          <div
            className="absolute inset-y-0 border-x-2 border-accent pointer-events-none"
            style={{ left: pct(start), right: `calc(100% - ${pct(end)})` }}
          />
          {/* handles */}
          <div
            className="absolute inset-y-0 -ml-1.5 w-3 cursor-ew-resize flex items-center justify-center"
            style={{ left: pct(start) }}
            onPointerDown={(e) => onTrackDown(e, "start")}
          >
            <div className="w-1 h-6 rounded bg-accent" />
          </div>
          <div
            className="absolute inset-y-0 -ml-1.5 w-3 cursor-ew-resize flex items-center justify-center"
            style={{ left: pct(end) }}
            onPointerDown={(e) => onTrackDown(e, "end")}
          >
            <div className="w-1 h-6 rounded bg-accent" />
          </div>
          {/* playhead */}
          <div className="absolute inset-y-0 w-0.5 bg-white/80 pointer-events-none" style={{ left: pct(current) }} />
        </div>

        <div className="flex items-center justify-between text-xs text-muted">
          <span>{t("editor.in")}: {tc(start)}</span>
          <span className="text-fg font-medium">{t("editor.selected")}: {tc(selDur)}</span>
          <span>{t("editor.out")}: {tc(end)}</span>
        </div>

        {/* options */}
        <div className="grid sm:grid-cols-2 gap-3">
          <button
            onClick={() => setCropOn((v) => !v)}
            className={clsx(
              "flex items-center gap-2 px-3 py-2 rounded-lg border text-sm transition",
              cropOn ? "border-accent text-accent bg-accent/10" : "border-border text-muted hover:text-fg"
            )}
          >
            <Crop className="w-4 h-4" /> {cropOn ? t("editor.cropOn") : t("editor.cropOff")}
          </button>

          <label className="flex items-center gap-2 px-3 py-2 rounded-lg border border-border text-sm">
            <Scissors className="w-4 h-4 text-muted shrink-0" />
            <span className="text-muted">{t("editor.split")}</span>
            <input
              type="number"
              min={1}
              max={20}
              value={split}
              onChange={(e) => setSplit(Math.max(1, Math.min(20, Number(e.target.value) || 1)))}
              className="w-16 rounded-md bg-surface border border-border px-2 py-1 text-sm"
            />
          </label>
        </div>

        {/* cut precision (only meaningful for a trim-only edit; a crop always re-encodes) */}
        {!cropOn ? (
          <div className="flex gap-2 text-sm">
            {(["accurate", "fast"] as const).map((m) => {
              const on = (m === "accurate") === accurate;
              return (
                <button
                  key={m}
                  onClick={() => setAccurate(m === "accurate")}
                  className={clsx(
                    "flex-1 px-3 py-2 rounded-lg border text-left transition",
                    on ? "border-accent bg-accent/10" : "border-border hover:border-muted"
                  )}
                >
                  <div className={clsx("font-medium", on ? "text-accent" : "text-fg")}>{t(`editor.${m}.label`)}</div>
                  <div className="text-xs text-muted mt-0.5">{t(`editor.${m}.hint`)}</div>
                </button>
              );
            })}
          </div>
        ) : (
          <p className="text-xs text-muted">{t("editor.cropReencode")}</p>
        )}

        <div>
          <label className="block text-sm font-medium mb-1">{t("editor.nameLabel")}</label>
          <input
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder={defaultName}
            className={inputCls}
          />
        </div>

        <div className="flex items-center justify-between pt-1">
          <span className="text-xs text-muted">
            {reencode ? t("editor.willReencode") : t("editor.willCopy")}
          </span>
          <div className="flex gap-2">
            <button onClick={onClose} className={clsx(btn, "text-muted hover:text-fg hover:bg-surface")}>
              {t("common.cancel")}
            </button>
            <button
              onClick={() => create.mutate()}
              disabled={!valid || create.isPending}
              className={clsx(btn, "bg-accent text-accent-fg hover:opacity-90 inline-flex items-center gap-1.5")}
            >
              {create.isPending && <Loader2 className="w-4 h-4 animate-spin" />}
              {split > 1 ? t("editor.createN", { count: Math.round(split) }) : t("editor.create")}
            </button>
          </div>
        </div>
      </div>
    </Modal>
  );
}
