import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

import {
  getAudioSettings,
  subscribeAudioSettings,
  updateAudioSettings,
} from '../lib/audioSettings';
import { useCall } from '../context/CallContext';
import { MicIcon, VideoIcon } from './icons';

interface Props {
  /** Title shown at the top — e.g. peer's display name. */
  title: string;
  /** Called when the user confirms — Modal closes itself first. */
  onJoin: () => void;
  /** Called when the user cancels or hits Esc. Modal closes itself first. */
  onCancel: () => void;
}

/**
 * Discord-style "before-you-join" preview for video calls. Shows the local
 * camera stream + mic/camera device selectors so the user sees what their
 * peers will see (and can fix backlight, etc.) before the call actually
 * starts. Resources are released on unmount so we never leak getUserMedia
 * tracks if the user backs out.
 */
export function CallPreviewModal({ title, onJoin, onCancel }: Props) {
  const { setVideoInputDevice, setAudioInputDevice } = useCall();
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const [cameras, setCameras] = useState<MediaDeviceInfo[]>([]);
  const [mics, setMics] = useState<MediaDeviceInfo[]>([]);
  const [cameraId, setCameraId] = useState<string | null>(
    () => getAudioSettings().videoInputDeviceId,
  );
  const [micId, setMicId] = useState<string | null>(
    () => getAudioSettings().inputDeviceId,
  );
  const [error, setError] = useState<string | null>(null);

  // Subscribe to settings so device-changes from another window propagate.
  useEffect(() => {
    return subscribeAudioSettings((s) => {
      setCameraId(s.videoInputDeviceId);
      setMicId(s.inputDeviceId);
    });
  }, []);

  // Acquire / re-acquire the preview stream when devices change. Each
  // re-acquire stops the previous stream so the camera light goes off
  // between switches and the new device actually starts streaming.
  useEffect(() => {
    let cancelled = false;
    const stopCurrent = () => {
      const cur = streamRef.current;
      if (cur) {
        cur.getTracks().forEach((t) => t.stop());
        streamRef.current = null;
      }
    };
    const acquire = async () => {
      stopCurrent();
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: cameraId ? { deviceId: { exact: cameraId } } : true,
          audio: micId ? { deviceId: { exact: micId } } : true,
        });
        if (cancelled) {
          stream.getTracks().forEach((t) => t.stop());
          return;
        }
        streamRef.current = stream;
        if (videoRef.current) videoRef.current.srcObject = stream;
        // After the first successful getUserMedia, enumerateDevices returns
        // labels — refresh so the dropdowns show real device names.
        try {
          const list = await navigator.mediaDevices.enumerateDevices();
          if (cancelled) return;
          setCameras(list.filter((d) => d.kind === 'videoinput'));
          setMics(list.filter((d) => d.kind === 'audioinput'));
        } catch {
          /* ignore */
        }
      } catch (err: unknown) {
        if (cancelled) return;
        setError(err instanceof Error ? err.message : 'Kamera nicht verfügbar');
      }
    };
    void acquire();
    return () => {
      cancelled = true;
      stopCurrent();
    };
  }, [cameraId, micId]);

  // Esc cancels.
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault();
        onCancel();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onCancel]);

  const handleJoin = () => {
    // Stop the preview stream BEFORE joining — LiveKit will start its own
    // capture, and on some devices the camera can't be opened twice.
    const cur = streamRef.current;
    if (cur) {
      cur.getTracks().forEach((t) => t.stop());
      streamRef.current = null;
    }
    onJoin();
  };

  return createPortal(
    <div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/70 p-6 backdrop-blur-sm motion-safe:animate-fade-in">
      <div
        role="dialog"
        aria-label={'Vorschau für ' + title}
        className="relative w-full max-w-[720px] motion-safe:animate-slide-up overflow-hidden rounded-2xl border border-line bg-surface-2 shadow-xl"
      >
        <div className="flex items-center justify-between border-b border-line px-5 py-3">
          <div>
            <p className="text-[11px] font-semibold uppercase tracking-[0.12em] text-fg-muted">
              Bereit für den Anruf?
            </p>
            <h2 className="mt-0.5 truncate font-display text-lg font-bold text-fg">
              {title}
            </h2>
          </div>
        </div>

        <div className="relative aspect-video w-full overflow-hidden bg-black">
          {error ? (
            <div className="flex h-full w-full flex-col items-center justify-center gap-2 px-6 text-center text-sm text-rose-400">
              <p className="font-semibold">Kein Kamerabild</p>
              <p className="text-xs text-fg-muted">{error}</p>
            </div>
          ) : (
            <video
              ref={videoRef}
              autoPlay
              playsInline
              muted
              className="h-full w-full scale-x-[-1] object-cover"
            />
          )}
        </div>

        <div className="space-y-3 px-5 py-4">
          <div>
            <label className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-fg-muted">
              <VideoIcon className="h-3.5 w-3.5" />
              Kamera
            </label>
            <select
              value={cameraId ?? ''}
              onChange={(e) => {
                const id = e.target.value === '' ? null : e.target.value;
                setCameraId(id);
                updateAudioSettings({ videoInputDeviceId: id });
                void setVideoInputDevice(id);
              }}
              className="mt-1 w-full rounded-lg border border-line bg-surface-3 px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30"
            >
              <option value="">Systemstandard</option>
              {cameras.map((d) => (
                <option key={d.deviceId} value={d.deviceId}>
                  {d.label || 'Unbenannt'}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-fg-muted">
              <MicIcon className="h-3.5 w-3.5" />
              Mikrofon
            </label>
            <select
              value={micId ?? ''}
              onChange={(e) => {
                const id = e.target.value === '' ? null : e.target.value;
                setMicId(id);
                updateAudioSettings({ inputDeviceId: id });
                void setAudioInputDevice(id);
              }}
              className="mt-1 w-full rounded-lg border border-line bg-surface-3 px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30"
            >
              <option value="">Systemstandard</option>
              {mics.map((d) => (
                <option key={d.deviceId} value={d.deviceId}>
                  {d.label || 'Unbenannt'}
                </option>
              ))}
            </select>
          </div>
        </div>

        <div className="flex justify-end gap-3 border-t border-line bg-surface-3/40 px-5 py-3">
          <button
            type="button"
            onClick={onCancel}
            className="cursor-pointer rounded-lg border border-line bg-transparent px-4 py-2 text-sm font-semibold text-fg-muted transition hover:bg-surface-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
          >
            Abbrechen
          </button>
          <button
            type="button"
            onClick={handleJoin}
            className="inline-flex cursor-pointer items-center gap-2 rounded-lg bg-accent px-4 py-2 text-sm font-semibold text-accent-fg shadow-accept-btn transition hover:brightness-110 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
          >
            <VideoIcon className="h-4 w-4" />
            Mit Video beitreten
          </button>
        </div>
      </div>
    </div>,
    document.body,
  );
}
