import {
  PLAY_ICON,
  PAUSE_ICON,
  VOLUME_HIGH_ICON,
  VOLUME_LOW_ICON,
  VOLUME_MUTED_ICON,
} from "./styles.js";

export interface ControlsCallbacks {
  onPlay: () => void;
  onPause: () => void;
  onSeek: (fraction: number) => void;
  /** Scrub drag started (mousedown/touchstart on the scrubber). */
  onScrubStart?: () => void;
  /** Scrub drag ended (mouseup/touchend). */
  onScrubEnd?: () => void;
  onSpeedChange: (speed: number) => void;
  onMuteToggle: () => void;
  onVolumeChange: (volume: number) => void;
}

/** Default logarithmic speed presets — each step roughly doubles/halves. */
export const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;

export interface ControlsOptions {
  /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */
  speedPresets?: readonly number[];
  /**
   * When true, the volume controls (mute button + volume slider) are hidden so
   * the viewer cannot change the audio state. Backs the `audio-locked`
   * attribute on `<hyperframes-player>`, which enforces host-mandated silent
   * playback (e.g. a HyperFrames project embedded in a chat host). Toggleable
   * at runtime via the returned `setVolumeControlsHidden`.
   */
  audioLocked?: boolean;
}

export function formatSpeed(speed: number): string {
  return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;
}

export function formatTime(seconds: number): string {
  // Handle non-finite values gracefully
  if (!Number.isFinite(seconds) || seconds < 0) {
    return "0:00";
  }
  const s = Math.floor(seconds);
  const m = Math.floor(s / 60);
  const sec = s % 60;
  return `${m}:${sec.toString().padStart(2, "0")}`;
}

export function createControls(
  parent: ShadowRoot | HTMLElement,
  callbacks: ControlsCallbacks,
  options: ControlsOptions = {},
): {
  updateTime: (current: number, duration: number) => void;
  updatePlaying: (playing: boolean) => void;
  updateSpeed: (speed: number) => void;
  updateMuted: (muted: boolean) => void;
  updateVolume: (volume: number) => void;
  setVolumeControlsHidden: (hidden: boolean) => void;
  show: () => void;
  hide: () => void;
  destroy: () => void;
} {
  const presets = options.speedPresets ?? SPEED_PRESETS;

  const controls = document.createElement("div");
  controls.className = "hfp-controls";
  // Keep overlay interactions from falling through to the host-level click toggle.
  controls.addEventListener("click", (e) => {
    e.stopPropagation();
  });

  const playBtn = document.createElement("button");
  playBtn.className = "hfp-play-btn";
  playBtn.type = "button";
  // Both glyphs stay in the DOM, stacked; toggling `hfp-playing` crossfade-morphs
  // between them (see styles.ts) instead of swapping innerHTML, which would kill
  // the transition.
  playBtn.innerHTML = `<span class="hfp-ico hfp-ico-play">${PLAY_ICON}</span><span class="hfp-ico hfp-ico-pause">${PAUSE_ICON}</span>`;
  playBtn.setAttribute("aria-label", "Play");

  const scrubber = document.createElement("div");
  scrubber.className = "hfp-scrubber";
  const progress = document.createElement("div");
  progress.className = "hfp-progress";
  progress.style.width = "0%";
  scrubber.appendChild(progress);

  const time = document.createElement("span");
  time.className = "hfp-time";
  time.textContent = "0:00 / 0:00";

  const speedWrap = document.createElement("div");
  speedWrap.className = "hfp-speed-wrap";

  const speedBtn = document.createElement("button");
  speedBtn.className = "hfp-speed-btn";
  speedBtn.type = "button";
  speedBtn.textContent = "1x";
  speedBtn.setAttribute("aria-label", "Playback speed");

  const speedMenu = document.createElement("div");
  speedMenu.className = "hfp-speed-menu";
  speedMenu.setAttribute("role", "menu");
  for (const preset of presets) {
    const item = document.createElement("button");
    item.className = "hfp-speed-option";
    item.type = "button";
    item.setAttribute("role", "menuitem");
    item.dataset.speed = String(preset);
    item.textContent = formatSpeed(preset);
    if (preset === 1) item.classList.add("hfp-active");
    speedMenu.appendChild(item);
  }

  speedWrap.appendChild(speedMenu);
  speedWrap.appendChild(speedBtn);

  const volumeWrap = document.createElement("div");
  volumeWrap.className = "hfp-volume-wrap";

  const muteBtn = document.createElement("button");
  muteBtn.className = "hfp-mute-btn";
  muteBtn.type = "button";
  muteBtn.innerHTML = VOLUME_HIGH_ICON;
  muteBtn.setAttribute("aria-label", "Mute");

  const volumeSliderWrap = document.createElement("div");
  volumeSliderWrap.className = "hfp-volume-slider-wrap";

  const volumeSlider = document.createElement("div");
  volumeSlider.className = "hfp-volume-slider";
  volumeSlider.setAttribute("role", "slider");
  volumeSlider.setAttribute("aria-label", "Volume");
  volumeSlider.setAttribute("aria-valuemin", "0");
  volumeSlider.setAttribute("aria-valuemax", "100");
  volumeSlider.setAttribute("aria-valuenow", "100");
  volumeSlider.tabIndex = 0;
  const volumeFill = document.createElement("div");
  volumeFill.className = "hfp-volume-fill";
  volumeFill.style.width = "100%";
  volumeSlider.appendChild(volumeFill);
  volumeSliderWrap.appendChild(volumeSlider);

  volumeWrap.appendChild(volumeSliderWrap);
  volumeWrap.appendChild(muteBtn);

  // Audio-locked: hide the whole volume control (mute toggle + slider) so the
  // viewer has no UI path to turn sound on. The player still mutes the media
  // independently via the `muted` attribute; this only removes the controls.
  if (options.audioLocked) volumeWrap.style.display = "none";

  controls.appendChild(playBtn);
  controls.appendChild(scrubber);
  controls.appendChild(time);
  controls.appendChild(volumeWrap);
  controls.appendChild(speedWrap);
  parent.appendChild(controls);

  let isPlaying = false;
  let isMuted = false;
  let currentVolume = 1;
  let hideTimeout: ReturnType<typeof setTimeout> | null = null;
  let speedIndex = presets.indexOf(1); // start at 1x
  if (speedIndex === -1) speedIndex = 0;

  const getVolumeIcon = (muted: boolean, volume: number): string => {
    if (muted) return VOLUME_MUTED_ICON;
    if (volume === 0) return VOLUME_LOW_ICON;
    if (volume < 0.5) return VOLUME_LOW_ICON;
    return VOLUME_HIGH_ICON;
  };

  playBtn.addEventListener("click", (e) => {
    e.stopPropagation();
    if (isPlaying) callbacks.onPause();
    else callbacks.onPlay();
  });

  muteBtn.addEventListener("click", (e) => {
    e.stopPropagation();
    callbacks.onMuteToggle();
  });

  let volumeScrubbing = false;

  const handleVolumeAt = (clientX: number) => {
    const rect = volumeSlider.getBoundingClientRect();
    const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
    currentVolume = fraction;
    volumeFill.style.width = `${fraction * 100}%`;
    volumeSlider.setAttribute("aria-valuenow", String(Math.round(fraction * 100)));
    if (isMuted && fraction > 0) callbacks.onMuteToggle();
    muteBtn.innerHTML = getVolumeIcon(isMuted, fraction);
    callbacks.onVolumeChange(fraction);
  };

  volumeSlider.addEventListener("mousedown", (e) => {
    e.stopPropagation();
    volumeScrubbing = true;
    handleVolumeAt(e.clientX);
  });
  const onVolumeMouseMove = (e: MouseEvent) => {
    if (volumeScrubbing) handleVolumeAt(e.clientX);
  };
  const onVolumeMouseUp = () => {
    volumeScrubbing = false;
  };
  document.addEventListener("mousemove", onVolumeMouseMove);
  document.addEventListener("mouseup", onVolumeMouseUp);

  volumeSlider.addEventListener(
    "touchstart",
    (e) => {
      volumeScrubbing = true;
      const touch = e.touches[0];
      if (touch) handleVolumeAt(touch.clientX);
    },
    { passive: true },
  );
  const onVolumeTouchMove = (e: TouchEvent) => {
    if (volumeScrubbing) {
      const touch = e.touches[0];
      if (touch) handleVolumeAt(touch.clientX);
    }
  };
  const onVolumeTouchEnd = () => {
    volumeScrubbing = false;
  };
  document.addEventListener("touchmove", onVolumeTouchMove, { passive: true });
  document.addEventListener("touchend", onVolumeTouchEnd);

  const VOLUME_STEP = 0.05;
  volumeSlider.addEventListener("keydown", (e) => {
    let newVol = currentVolume;
    if (e.key === "ArrowRight" || e.key === "ArrowUp") {
      newVol = Math.min(1, currentVolume + VOLUME_STEP);
    } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
      newVol = Math.max(0, currentVolume - VOLUME_STEP);
    } else {
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    currentVolume = newVol;
    volumeFill.style.width = `${newVol * 100}%`;
    volumeSlider.setAttribute("aria-valuenow", String(Math.round(newVol * 100)));
    if (isMuted && newVol > 0) callbacks.onMuteToggle();
    muteBtn.innerHTML = getVolumeIcon(isMuted, newVol);
    callbacks.onVolumeChange(newVol);
  });

  const setActiveOption = (speed: number) => {
    for (const opt of speedMenu.querySelectorAll(".hfp-speed-option")) {
      opt.classList.toggle("hfp-active", (opt as HTMLElement).dataset.speed === String(speed));
    }
  };

  speedBtn.addEventListener("click", (e) => {
    e.stopPropagation();
    const isOpen = speedMenu.classList.toggle("hfp-open");
    speedBtn.setAttribute("aria-expanded", String(isOpen));
  });

  speedMenu.addEventListener("click", (e) => {
    e.stopPropagation();
    const target = (e.target as HTMLElement).closest(".hfp-speed-option") as HTMLElement | null;
    if (!target) return;
    const newSpeed = parseFloat(target.dataset.speed!);
    speedIndex = presets.indexOf(newSpeed);
    speedBtn.textContent = formatSpeed(newSpeed);
    setActiveOption(newSpeed);
    speedMenu.classList.remove("hfp-open");
    speedBtn.setAttribute("aria-expanded", "false");
    callbacks.onSpeedChange(newSpeed);
  });

  // Close menu when clicking outside
  const onDocClick = () => {
    speedMenu.classList.remove("hfp-open");
    speedBtn.setAttribute("aria-expanded", "false");
  };
  document.addEventListener("click", onDocClick);

  const handleScrubAt = (clientX: number) => {
    const rect = scrubber.getBoundingClientRect();
    const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
    callbacks.onSeek(fraction);
  };

  let scrubbing = false;

  scrubber.addEventListener("mousedown", (e) => {
    e.stopPropagation();
    scrubbing = true;
    callbacks.onScrubStart?.();
    handleScrubAt(e.clientX);
  });
  const onMouseMove = (e: MouseEvent) => {
    if (scrubbing) handleScrubAt(e.clientX);
  };
  const onMouseUp = () => {
    if (scrubbing) {
      scrubbing = false;
      callbacks.onScrubEnd?.();
    }
  };
  document.addEventListener("mousemove", onMouseMove);
  document.addEventListener("mouseup", onMouseUp);

  scrubber.addEventListener(
    "touchstart",
    (e) => {
      scrubbing = true;
      callbacks.onScrubStart?.();
      const touch = e.touches[0];
      if (touch) handleScrubAt(touch.clientX);
    },
    { passive: true },
  );
  const onTouchMove = (e: TouchEvent) => {
    if (scrubbing) {
      const touch = e.touches[0];
      if (touch) handleScrubAt(touch.clientX);
    }
  };
  const onTouchEnd = () => {
    if (scrubbing) {
      scrubbing = false;
      callbacks.onScrubEnd?.();
    }
  };
  document.addEventListener("touchmove", onTouchMove, { passive: true });
  document.addEventListener("touchend", onTouchEnd);

  const startHideTimer = () => {
    if (hideTimeout) clearTimeout(hideTimeout);
    hideTimeout = setTimeout(() => {
      if (isPlaying) controls.classList.add("hfp-hidden");
    }, 3000);
  };

  const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;
  const onHostMouseMove = () => {
    controls.classList.remove("hfp-hidden");
    startHideTimer();
  };
  const onHostMouseLeave = () => {
    if (isPlaying) controls.classList.add("hfp-hidden");
  };
  host.addEventListener("mousemove", onHostMouseMove);
  host.addEventListener("mouseleave", onHostMouseLeave);

  return {
    updateTime(current: number, duration: number) {
      // Defensive: source should already clamp, but guard here so the UI never overflows.
      const clampedCurrent = duration > 0 ? Math.min(current, duration) : current;
      const pct = duration > 0 ? (clampedCurrent / duration) * 100 : 0;
      progress.style.width = `${pct}%`;
      time.textContent = `${formatTime(clampedCurrent)} / ${formatTime(duration)}`;
    },
    updatePlaying(playing: boolean) {
      isPlaying = playing;
      playBtn.classList.toggle("hfp-playing", playing);
      playBtn.setAttribute("aria-label", playing ? "Pause" : "Play");
      if (playing) startHideTimer();
      else controls.classList.remove("hfp-hidden");
    },
    updateSpeed(speed: number) {
      const idx = presets.indexOf(speed);
      if (idx !== -1) speedIndex = idx;
      speedBtn.textContent = formatSpeed(speed);
      setActiveOption(speed);
    },
    updateMuted(muted: boolean) {
      isMuted = muted;
      muteBtn.innerHTML = getVolumeIcon(muted, currentVolume);
      muteBtn.setAttribute("aria-label", muted ? "Unmute" : "Mute");
    },
    updateVolume(volume: number) {
      currentVolume = volume;
      volumeFill.style.width = `${volume * 100}%`;
      volumeSlider.setAttribute("aria-valuenow", String(Math.round(volume * 100)));
      muteBtn.innerHTML = getVolumeIcon(isMuted, volume);
    },
    setVolumeControlsHidden(hidden: boolean) {
      volumeWrap.style.display = hidden ? "none" : "";
    },
    show() {
      controls.style.display = "";
    },
    hide() {
      controls.style.display = "none";
    },
    destroy() {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
      document.removeEventListener("touchmove", onTouchMove);
      document.removeEventListener("touchend", onTouchEnd);
      document.removeEventListener("mousemove", onVolumeMouseMove);
      document.removeEventListener("mouseup", onVolumeMouseUp);
      document.removeEventListener("touchmove", onVolumeTouchMove);
      document.removeEventListener("touchend", onVolumeTouchEnd);
      document.removeEventListener("click", onDocClick);
      host.removeEventListener("mousemove", onHostMouseMove);
      host.removeEventListener("mouseleave", onHostMouseLeave);
      if (hideTimeout) clearTimeout(hideTimeout);
      controls.remove();
    },
  };
}
