import { Maximize2, Pause, Play } from "lucide-react";
import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type MouseEvent as ReactMouseEvent,
  type PointerEvent as ReactPointerEvent,
} from "react";
import type { Attachment } from "../../types/post";
import { AttachmentDownloadPill } from "./AttachmentDownloadPill";
import { useVideoPlayer } from "./overlays/VideoPlayer";

function pad2(n: number): string {
  return String(Math.floor(n)).padStart(2, "0");
}

function formatClock(sec: number): string {
  if (!Number.isFinite(sec) || sec < 0) return "0:00";
  const m = Math.floor(sec / 60);
  const s = Math.floor(sec % 60);
  return `${m}:${pad2(s)}`;
}

type Size = "sm" | "lg";

type SizeTokens = {
  /** Padding / gap of the bottom controls bar. */
  bar: string;
  /** Square hit area of the play/pause and fullscreen buttons. */
  btn: string;
  /** Lucide icon inside the bar buttons. */
  btnIcon: string;
  /** Center play affordance shown while paused. */
  centerBox: string;
  /** Lucide icon inside the centered play affordance. */
  centerIcon: string;
  /** Hit area of the scrub bar wrapper. */
  scrubRow: string;
  /** Visible fill rail. */
  scrubRail: string;
  /** Drag handle dot. */
  scrubHandle: string;
  /** Time text. */
  timeText: string;
};

const TOKENS: Record<Size, SizeTokens> = {
  sm: {
    bar: "gap-2 px-3 pb-2 pt-6 text-[12px]",
    btn: "h-7 w-7",
    btnIcon: "h-4 w-4",
    centerBox: "h-14 w-14 md:h-16 md:w-16",
    centerIcon: "h-6 w-6",
    scrubRow: "h-5",
    scrubRail: "h-1",
    scrubHandle: "h-3 w-3",
    timeText: "",
  },
  lg: {
    bar: "gap-3 px-5 pb-4 pt-10 text-[14px]",
    btn: "h-10 w-10",
    btnIcon: "h-5 w-5",
    centerBox: "h-20 w-20 md:h-24 md:w-24",
    centerIcon: "h-8 w-8",
    scrubRow: "h-7",
    scrubRail: "h-1.5",
    scrubHandle: "h-4 w-4",
    timeText: "text-[14px]",
  },
};

/**
 * Cross-platform inline video player with custom controls. Disables every
 * native control overlay (iOS Safari's `playsInline` UI is otherwise
 * impossible to fully tame via CSS) and reimplements the essentials:
 *
 *   - Centered play affordance while paused.
 *   - Bottom bar with play/pause, current time, scrub bar, total time, and
 *     a fullscreen button.
 *   - Tap on the video toggles play/pause.
 *   - Click or drag on the scrub bar seeks live to that point (mouse +
 *     touch, via pointer events with pointer capture).
 *
 * The download pill is preserved at the top-left so it lives alongside the
 * existing visual language of the message bubbles.
 */
export function MessageInlineVideo({
  postId,
  attachment,
  initialTime = 0,
  autoPlay = true,
  leadingLabel,
  hideDownload = false,
  hideFullscreen = false,
  onTimeUpdate,
  size = "sm",
}: {
  postId: string;
  attachment: Attachment;
  initialTime?: number;
  autoPlay?: boolean;
  leadingLabel?: string;
  /** Suppress the top-left download pill (overlay supplies its own). */
  hideDownload?: boolean;
  /** Suppress the fullscreen button (when we are already in fullscreen). */
  hideFullscreen?: boolean;
  /** Reports the playhead to the parent on every native `timeupdate`. */
  onTimeUpdate?: (currentTime: number) => void;
  /**
   * `sm` (default) for bubble-sized inline players. `lg` for surfaces like
   * the fullscreen overlay where controls should feel touch-friendly.
   */
  size?: Size;
}) {
  const { openVideo } = useVideoPlayer();
  const videoRef = useRef<HTMLVideoElement>(null);
  const scrubRef = useRef<HTMLDivElement>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(initialTime);
  const [duration, setDuration] = useState(attachment.durationSec ?? 0);
  const [isScrubbing, setIsScrubbing] = useState(false);

  const t = TOKENS[size];

  useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    const onPlay = () => setIsPlaying(true);
    const onPause = () => setIsPlaying(false);
    const onTime = () => {
      setCurrentTime(v.currentTime);
      onTimeUpdate?.(v.currentTime);
    };
    // `seeked` fires even when the video is paused, so an external
    // `currentTime = ...` (e.g. syncing back from fullscreen) reaches state
    // immediately instead of waiting for the next `timeupdate`.
    const onSeeked = () => {
      setCurrentTime(v.currentTime);
      onTimeUpdate?.(v.currentTime);
    };
    const onMeta = () => {
      if (Number.isFinite(v.duration)) setDuration(v.duration);
      if (initialTime > 0) {
        try {
          v.currentTime = initialTime;
        } catch {
          // Ignore out-of-range seeks.
        }
      }
    };
    v.addEventListener("play", onPlay);
    v.addEventListener("pause", onPause);
    v.addEventListener("timeupdate", onTime);
    v.addEventListener("seeked", onSeeked);
    v.addEventListener("loadedmetadata", onMeta);
    return () => {
      v.removeEventListener("play", onPlay);
      v.removeEventListener("pause", onPause);
      v.removeEventListener("timeupdate", onTime);
      v.removeEventListener("seeked", onSeeked);
      v.removeEventListener("loadedmetadata", onMeta);
    };
  }, [initialTime, onTimeUpdate]);

  const togglePlay = useCallback(() => {
    const v = videoRef.current;
    if (!v) return;
    if (v.paused) v.play().catch(() => {});
    else v.pause();
  }, []);

  const seekToClientX = useCallback((clientX: number) => {
    const el = scrubRef.current;
    const v = videoRef.current;
    if (!el || !v || !Number.isFinite(v.duration)) return;
    const rect = el.getBoundingClientRect();
    const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
    const next = ratio * v.duration;
    try {
      v.currentTime = next;
    } catch {
      // Ignore out-of-range seeks.
    }
    setCurrentTime(next);
  }, []);

  const onScrubPointerDown = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      e.preventDefault();
      e.currentTarget.setPointerCapture(e.pointerId);
      setIsScrubbing(true);
      seekToClientX(e.clientX);
    },
    [seekToClientX],
  );

  const onScrubPointerMove = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      if (!isScrubbing) return;
      seekToClientX(e.clientX);
    },
    [isScrubbing, seekToClientX],
  );

  const onScrubPointerEnd = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      if (!isScrubbing) return;
      try {
        e.currentTarget.releasePointerCapture(e.pointerId);
      } catch {
        // Pointer may already have been released.
      }
      setIsScrubbing(false);
    },
    [isScrubbing],
  );

  const goFullscreen = useCallback(
    (e: ReactMouseEvent<HTMLButtonElement>) => {
      e.stopPropagation();
      const v = videoRef.current;
      if (!v) return;
      const resumeAt = Number.isFinite(v.currentTime) ? v.currentTime : 0;
      v.pause();
      openVideo(
        attachment,
        resumeAt,
        (finalTime) => {
          const inline = videoRef.current;
          if (!inline || !Number.isFinite(finalTime)) return;
          // Update React state synchronously so the progress bar paints the
          // new playhead in the next frame, before the <video> seek round-
          // trip emits its own events (paused videos don't fire timeupdate
          // and `seeked` can lag ~hundreds of ms).
          setCurrentTime(finalTime);
          onTimeUpdate?.(finalTime);
          const apply = () => {
            try {
              inline.currentTime = finalTime;
            } catch {
              // Ignore out-of-range seeks.
            }
          };
          if (inline.readyState >= 1) apply();
          else inline.addEventListener("loadedmetadata", apply, { once: true });
        },
        postId,
      );
    },
    [attachment, openVideo, postId],
  );

  const progressPct = duration > 0 ? (currentTime / duration) * 100 : 0;
  const remaining = Math.max(0, duration - currentTime);
  const handleOffset = size === "lg" ? 8 : 6;

  return (
    <>
      <video
        ref={videoRef}
        src={attachment.url}
        poster={attachment.posterUrl}
        playsInline
        autoPlay={autoPlay}
        onClick={togglePlay}
        className="absolute inset-0 h-full w-full object-contain"
      />

      {hideDownload ? null : (
        <AttachmentDownloadPill
          postId={postId}
          attachment={attachment}
          leadingLabel={leadingLabel}
          className="absolute left-2 top-2 z-20"
        />
      )}

      {!isPlaying ? (
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation();
            togglePlay();
          }}
          className="absolute inset-0 z-10 flex items-center justify-center"
          aria-label="Play"
        >
          <span
            className={`flex items-center justify-center rounded-full bg-black/60 text-white shadow-lg ring-1 ring-white/15 backdrop-blur-md transition group-hover:bg-black/70 ${t.centerBox}`}
          >
            <Play className={`translate-x-0.5 fill-white ${t.centerIcon}`} />
          </span>
        </button>
      ) : null}

      <div
        className={`absolute inset-x-0 bottom-0 z-20 flex items-center bg-gradient-to-t from-black/85 via-black/45 to-transparent leading-none text-white ${t.bar}`}
        onClick={(e) => e.stopPropagation()}
      >
        <button
          type="button"
          onClick={togglePlay}
          className={`flex shrink-0 items-center justify-center text-white transition hover:scale-105 ${t.btn}`}
          aria-label={isPlaying ? "Pause" : "Play"}
        >
          {isPlaying ? (
            <Pause className={`fill-white ${t.btnIcon}`} />
          ) : (
            <Play className={`translate-x-0.5 fill-white ${t.btnIcon}`} />
          )}
        </button>

        <span className={`shrink-0 tabular-nums text-white ${t.timeText}`}>
          {formatClock(currentTime)}
        </span>

        <div
          ref={scrubRef}
          role="slider"
          aria-label="Seek"
          aria-valuemin={0}
          aria-valuemax={duration || 0}
          aria-valuenow={currentTime}
          tabIndex={0}
          onPointerDown={onScrubPointerDown}
          onPointerMove={onScrubPointerMove}
          onPointerUp={onScrubPointerEnd}
          onPointerCancel={onScrubPointerEnd}
          className={`group relative min-w-0 flex-1 cursor-pointer touch-none select-none ${t.scrubRow}`}
        >
          <div
            className={`absolute inset-x-0 top-1/2 -translate-y-1/2 overflow-hidden rounded-full bg-white/25 ${t.scrubRail}`}
          >
            <div
              className={`h-full bg-white ${
                isScrubbing ? "" : "transition-[width] duration-150 ease-out"
              }`}
              style={{ width: `${progressPct}%` }}
            />
          </div>
          <div
            aria-hidden
            className={`pointer-events-none absolute top-1/2 -translate-y-1/2 rounded-full bg-white shadow-lg transition-transform duration-150 ease-out ${
              isScrubbing ? "scale-125" : "scale-100"
            } ${t.scrubHandle}`}
            style={{ left: `calc(${progressPct}% - ${handleOffset}px)` }}
          />
        </div>

        <span className={`shrink-0 tabular-nums text-white/80 ${t.timeText}`}>
          -{formatClock(remaining)}
        </span>

        {hideFullscreen ? null : (
          <button
            type="button"
            onClick={goFullscreen}
            className={`flex shrink-0 items-center justify-center text-white transition hover:scale-105 ${t.btn}`}
            aria-label="Fullscreen"
          >
            <Maximize2 className={t.btnIcon} strokeWidth={2.2} />
          </button>
        )}
      </div>
    </>
  );
}
