import { useCallback, useEffect, useState } from "react";

export type PermissionState = "unknown" | "granted" | "denied";

interface UseMediaDevicesResult {
  audioInputs: MediaDeviceInfo[];
  videoInputs: MediaDeviceInfo[];
  permissionState: PermissionState;
  refresh: () => Promise<void>;
  requestLabels: () => Promise<void>;
  error: string | null;
}

/**
 * Enumerates input devices and stays subscribed to `devicechange`.
 *
 * Labels are only populated after the user has granted mic/camera
 * permission — `requestLabels` triggers a brief getUserMedia so that
 * subsequent enumerations return human-readable names, matching the
 * pattern most video-conferencing apps use.
 */
export function useMediaDevices(): UseMediaDevicesResult {
  const [devices, setDevices] = useState<MediaDeviceInfo[]>([]);
  const [permissionState, setPermissionState] =
    useState<PermissionState>("unknown");
  const [error, setError] = useState<string | null>(null);

  const refresh = useCallback(async () => {
    try {
      const list = await navigator.mediaDevices.enumerateDevices();
      setDevices(list);
      // If at least one input device has a non-empty label, permission
      // has been granted at some point for that device kind.
      const hasLabels = list.some(
        (d) =>
          (d.kind === "audioinput" || d.kind === "videoinput") &&
          d.label.length > 0,
      );
      if (hasLabels) setPermissionState("granted");
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to list devices");
    }
  }, []);

  const requestLabels = useCallback(async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: true,
      });
      // Immediately stop — we only needed the permission grant.
      stream.getTracks().forEach((t) => t.stop());
      setPermissionState("granted");
      setError(null);
      await refresh();
    } catch (err) {
      setPermissionState("denied");
      setError(
        err instanceof Error ? err.message : "Microphone/camera access denied",
      );
    }
  }, [refresh]);

  useEffect(() => {
    refresh();
    const handle = () => {
      refresh();
    };
    navigator.mediaDevices.addEventListener("devicechange", handle);
    return () => {
      navigator.mediaDevices.removeEventListener("devicechange", handle);
    };
  }, [refresh]);

  return {
    audioInputs: devices.filter((d) => d.kind === "audioinput"),
    videoInputs: devices.filter((d) => d.kind === "videoinput"),
    permissionState,
    refresh,
    requestLabels,
    error,
  };
}
