'use client';

import {
  forwardRef,
  useCallback,
  useEffect,
  useRef,
  useState,
  type ButtonHTMLAttributes,
  type ReactNode,
} from 'react';
import { Volume2, VolumeX, Volume1 } from 'lucide-react';
import { cn } from '@djangocfg/ui-core/lib';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
  Slider,
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from '@djangocfg/ui-core/components';

export interface VolumeProps {
  readonly className?: string;
  /**
   * Reserved for backwards compatibility. The control is always a single
   * icon button that opens a popover with a vertical slider on hover/focus,
   * so this flag is now a no-op. Kept so existing consumers
   * (`<Volume iconOnly />`) don't break.
   */
  readonly iconOnly?: boolean;
  /** Mute-button tooltip copy. Defaults to `"Volume"`. Pass `false` to opt out. */
  readonly muteLabel?: ReactNode | false;
}

// Hide the popover slider on iOS Safari — `video.volume` is read-only
// there (controlled by hardware buttons), so a JS slider does nothing.
// The trigger still toggles mute, which iOS *does* honour.
function isIosSafari(): boolean {
  if (typeof navigator === 'undefined') return false;
  const ua = navigator.userAgent;
  const iOS =
    /iPad|iPhone|iPod/.test(ua) ||
    (navigator.platform === 'MacIntel' &&
      (navigator as { maxTouchPoints?: number }).maxTouchPoints! > 1);
  return iOS;
}

// media-chrome request event names. Dispatched on any descendant of
// `<media-controller>`; the controller catches them and updates its
// internal store, which then propagates state back via attributes.
const MEDIA_VOLUME_REQUEST = 'mediavolumerequest';
const MEDIA_MUTE_REQUEST = 'mediamuterequest';
const MEDIA_UNMUTE_REQUEST = 'mediaunmuterequest';

/**
 * Click-style trigger that visually matches the rest of the VideoPlayer
 * control bar. Mirrors `.video-player__control` styling from
 * `styles/video-player.css` so it slots in next to `<MediaPlayButton>` &
 * friends without standing out.
 */
const TriggerButton = forwardRef<
  HTMLButtonElement,
  ButtonHTMLAttributes<HTMLButtonElement>
>(function TriggerButton({ className, children, ...rest }, ref) {
  return (
    <button
      ref={ref}
      type="button"
      className={cn(
        'video-player__control',
        'grid h-8 w-8 place-items-center rounded-md',
        'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60',
        className,
      )}
      {...rest}
    >
      {children}
    </button>
  );
});

/**
 * VideoPlayer volume control.
 *
 * - **Click** the trigger → mute / unmute toggle (dispatches a
 *   `mediamuterequest` / `mediaunmuterequest` so media-chrome's store
 *   stays the single source of truth — no parallel state machine).
 * - **Hover / keyboard focus** → opens a popover containing a vertical
 *   `<Slider>` (0..100). Drag = volume change via `mediavolumerequest`.
 * - Icon reflects level (mute / low / high) and mirrors `media.muted`.
 *
 * State is read directly off the underlying `HTMLMediaElement` (located
 * via the closest `<media-controller>`'s `.media` accessor); we never
 * shadow media-chrome's store.
 */
export function Volume({ className, muteLabel }: VolumeProps) {
  const triggerRef = useRef<HTMLButtonElement | null>(null);
  const [volume, setVolume] = useState(1);
  const [muted, setMuted] = useState(false);
  const [open, setOpen] = useState(false);
  const [iosSafari] = useState(isIosSafari);

  // Resolve & track the underlying HTMLMediaElement so we can read live
  // volume / muted. media-chrome fires `mediaelementchange` on the
  // controller whenever the slotted media element swaps (e.g. source
  // change), so we re-bind listeners on each swap.
  useEffect(() => {
    const trigger = triggerRef.current;
    if (!trigger) return;
    const controller = trigger.closest('media-controller') as
      | (HTMLElement & { media?: HTMLMediaElement | null })
      | null;
    if (!controller) return;

    let media: HTMLMediaElement | null = controller.media ?? null;
    const sync = () => {
      if (!media) return;
      setVolume(media.volume);
      setMuted(media.muted);
    };
    const bind = (el: HTMLMediaElement | null) => {
      if (!el) return;
      el.addEventListener('volumechange', sync);
      sync();
    };
    const unbind = (el: HTMLMediaElement | null) => {
      if (!el) return;
      el.removeEventListener('volumechange', sync);
    };

    bind(media);

    const onMediaChange = (e: Event) => {
      unbind(media);
      const detail = (e as CustomEvent).detail as HTMLMediaElement | null;
      media = detail ?? controller.media ?? null;
      bind(media);
    };
    controller.addEventListener('mediaelementchange', onMediaChange);

    return () => {
      unbind(media);
      controller.removeEventListener('mediaelementchange', onMediaChange);
    };
  }, []);

  const dispatch = useCallback((name: string, detail?: number) => {
    const trigger = triggerRef.current;
    if (!trigger) return;
    trigger.dispatchEvent(
      new CustomEvent(name, { detail, bubbles: true, composed: true }),
    );
  }, []);

  const toggleMute = useCallback(() => {
    dispatch(muted ? MEDIA_UNMUTE_REQUEST : MEDIA_MUTE_REQUEST);
  }, [dispatch, muted]);

  const onSlider = useCallback(
    (value: number) => {
      dispatch(MEDIA_VOLUME_REQUEST, value);
      // If the user nudges the slider above zero, treat that as an
      // implicit unmute — matches what the native HTML5 volume slider does.
      if (value > 0 && muted) dispatch(MEDIA_UNMUTE_REQUEST);
    },
    [dispatch, muted],
  );

  const effectiveVolume = muted ? 0 : volume;
  const Icon =
    muted || volume === 0 ? VolumeX : volume < 0.5 ? Volume1 : Volume2;

  const trigger = (
    <TriggerButton
      ref={triggerRef}
      aria-label={muted ? 'Unmute' : 'Mute'}
      aria-pressed={muted}
      onClick={toggleMute}
    >
      <Icon className="h-4 w-4" strokeWidth={1.75} />
    </TriggerButton>
  );

  // On iOS Safari volume is hardware-only — skip the popover entirely
  // and let the trigger work as a plain mute/unmute button.
  if (iosSafari) {
    if (muteLabel === false) return <div className={cn('flex', className)}>{trigger}</div>;
    return (
      <div className={cn('flex items-center', className)}>
        <Tooltip>
          <TooltipTrigger asChild>{trigger}</TooltipTrigger>
          <TooltipContent side="top">{muteLabel ?? 'Mute / unmute'}</TooltipContent>
        </Tooltip>
      </div>
    );
  }

  return (
    <div
      className={cn('flex items-center', className)}
      // Hover-driven open: opening on pointerenter of the wrapper (not
      // just the button) gives the user a forgiving hit-area when their
      // cursor drifts between trigger and popover. The popover itself
      // also catches pointerenter via Radix portal — so once it's open,
      // moving into it keeps it open.
      onPointerEnter={() => setOpen(true)}
      onPointerLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)}
      onBlur={(e) => {
        // Don't close when focus moves between the trigger and the
        // popover content (both live in this wrapper / its portal).
        if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {
          setOpen(false);
        }
      }}
    >
      <Popover open={open} onOpenChange={setOpen}>
        <PopoverTrigger asChild>{trigger}</PopoverTrigger>
        <PopoverContent
          side="top"
          align="center"
          sideOffset={8}
          // Keep focus on the trigger so click-to-mute still works while
          // the popover is visible; the slider remains keyboard-reachable
          // via Tab once a user explicitly tabs into it.
          onOpenAutoFocus={(e) => e.preventDefault()}
          onCloseAutoFocus={(e) => e.preventDefault()}
          onPointerEnter={() => setOpen(true)}
          onPointerLeave={() => setOpen(false)}
          className="flex w-12 flex-col items-center gap-2 p-3"
        >
          <span className="w-full text-center text-[10px] tabular-nums text-muted-foreground">
            {Math.round(effectiveVolume * 100)}
          </span>
          <Slider
            orientation="vertical"
            min={0}
            max={1}
            step={0.01}
            value={[effectiveVolume]}
            onValueChange={([v]) => onSlider(v)}
            aria-label="Volume"
            className="h-24"
          />
        </PopoverContent>
      </Popover>
    </div>
  );
}
