import { useEffect, useRef, useState } from 'react';
import { api, type SoundboardSound } from '../../api/client';
import { wsSend } from '../../hooks/useWebSocket';
import { useTransferStore } from '../../stores/transferStore';
import { waitForTransferAttachment } from '../../utils/waitForTransfer';
import { useT } from '../../i18n';

interface SoundboardPopoverProps {
  spaceId: string;
  canManage: boolean;
  onClose: () => void;
}

/** Clips are short gags; anything larger is a music file in disguise. */
const MAX_SOUND_BYTES = 2 * 1024 * 1024;

export function SoundboardPopover({ spaceId, canManage, onClose }: SoundboardPopoverProps) {
  const t = useT();
  const [sounds, setSounds] = useState<SoundboardSound[]>([]);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState('');
  // Two-step add: pick the file, then name it in a field right here. The first
  // version asked with window.prompt, which Electron does not implement — it
  // returned nothing and the flow aborted in silence, so adding a sound worked
  // in the browser and did nothing at all in the desktop app.
  const [pendingFile, setPendingFile] = useState<File | null>(null);
  const [pendingName, setPendingName] = useState('');
  const fileRef = useRef<HTMLInputElement>(null);
  const panelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let cancelled = false;
    api.soundboard.list(spaceId)
      .then(({ sounds: list }) => { if (!cancelled) setSounds(list); })
      .catch(() => { /* an empty board is the honest fallback */ });
    return () => { cancelled = true; };
  }, [spaceId]);

  useEffect(() => {
    const handlePointer = (e: MouseEvent | TouchEvent) => {
      if (!panelRef.current?.contains(e.target as Node)) onClose();
    };
    const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('mousedown', handlePointer);
    document.addEventListener('touchstart', handlePointer);
    document.addEventListener('keydown', handleKey);
    return () => {
      document.removeEventListener('mousedown', handlePointer);
      document.removeEventListener('touchstart', handlePointer);
      document.removeEventListener('keydown', handleKey);
    };
  }, [onClose]);

  // Fire and forget: the server echoes the clip back to everyone in the call,
  // this client included, so the presser hears exactly what the others hear —
  // including the server's refusal when the cooldown is still running.
  const play = (soundId: string) => wsSend({ type: 'soundboard_play', soundId });

  const pickFile = (file: File) => {
    setError('');
    if (file.size > MAX_SOUND_BYTES) {
      setError(t('soundboard.tooLarge'));
      return;
    }
    setPendingFile(file);
    setPendingName(file.name.replace(/\.[^.]+$/, '').slice(0, 32));
  };

  const cancelPending = () => {
    setPendingFile(null);
    setPendingName('');
    if (fileRef.current) fileRef.current.value = '';
  };

  const confirmPending = async () => {
    const file = pendingFile;
    const name = pendingName.trim();
    if (!file || !name) return;

    setUploading(true);
    setError('');
    try {
      const tid = await useTransferStore.getState().startUpload(file, { tray: false });
      const { filename } = await waitForTransferAttachment(tid);
      const created = await api.soundboard.add(spaceId, name, filename);
      setSounds((prev) => [...prev, created]);
      cancelPending();
    } catch {
      // Distinct from the size check above: reporting every failure as "too
      // large" sends people to shrink a file that was never the problem.
      setError(t('soundboard.uploadFailed'));
    } finally {
      setUploading(false);
    }
  };

  const handleRemove = async (soundId: string) => {
    const previous = sounds;
    setSounds((prev) => prev.filter((s) => s.id !== soundId));
    try {
      await api.soundboard.remove(soundId);
    } catch {
      setSounds(previous);
    }
  };

  return (
    <div
      ref={panelRef}
      className="absolute bottom-full left-2 right-2 mb-2 z-[200] glass rounded-xl overflow-hidden p-3 shadow-xl"
    >
      <div className="flex items-center justify-between mb-2">
        <span className="text-[12px] font-semibold uppercase tracking-wider text-txt-tertiary">
          {t('soundboard.title')}
        </span>
        {canManage && (
          <>
            <button
              type="button"
              onClick={() => fileRef.current?.click()}
              disabled={uploading}
              className="text-[11px] text-accent-primary hover:underline disabled:opacity-50"
            >
              {uploading ? t('soundboard.adding') : t('soundboard.add')}
            </button>
            <input
              ref={fileRef}
              type="file"
              accept="audio/*"
              className="hidden"
              onChange={(e) => {
                const file = e.target.files?.[0];
                if (file) pickFile(file);
              }}
            />
          </>
        )}
      </div>

      {error && <div className="text-[11px] text-txt-danger mb-2">{error}</div>}

      {pendingFile && (
        <div className="mb-2 p-2 rounded-lg bg-surface-elevated/60">
          <label className="block text-[11px] text-txt-tertiary mb-1">
            {t('soundboard.namePrompt')}
          </label>
          <input
            type="text"
            value={pendingName}
            maxLength={32}
            autoFocus
            onChange={(e) => setPendingName(e.target.value)}
            onKeyDown={(e) => {
              // Scoped here so Enter does not reach the composer behind the popover.
              e.stopPropagation();
              if (e.key === 'Enter' && pendingName.trim()) void confirmPending();
              if (e.key === 'Escape') cancelPending();
            }}
            className="input-search w-full mb-2"
          />
          <div className="flex gap-2">
            <button
              type="button"
              onClick={() => void confirmPending()}
              disabled={uploading || !pendingName.trim()}
              className="px-2.5 py-1 rounded-md text-[11px] font-medium bg-accent-primary text-white disabled:opacity-50"
            >
              {uploading ? t('soundboard.adding') : t('soundboard.confirm')}
            </button>
            <button
              type="button"
              onClick={cancelPending}
              disabled={uploading}
              className="px-2.5 py-1 rounded-md text-[11px] font-medium bg-interactive-muted text-txt-secondary disabled:opacity-50"
            >
              {t('soundboard.cancel')}
            </button>
          </div>
        </div>
      )}

      {sounds.length === 0 ? (
        <p className="text-[12px] text-txt-tertiary py-2">{t('soundboard.empty')}</p>
      ) : (
        <div className="grid grid-cols-3 gap-1.5 max-h-[220px] overflow-y-auto scrollbar-thin">
          {sounds.map((sound) => (
            <div key={sound.id} className="relative group">
              <button
                type="button"
                onClick={() => play(sound.id)}
                className="w-full px-2 py-2.5 rounded-lg bg-surface-elevated text-txt-secondary hover:text-txt-primary hover:brightness-125 transition-all text-[11px] font-medium truncate"
                title={sound.name}
              >
                {sound.name}
              </button>
              {canManage && (
                <button
                  type="button"
                  onClick={() => void handleRemove(sound.id)}
                  title={t('soundboard.remove')}
                  aria-label={t('soundboard.remove')}
                  className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-accent-rose text-white text-[10px] leading-none opacity-0 group-hover:opacity-100 transition-opacity"
                >
                  ×
                </button>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}
