"use client";

import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import VoiceOrb from './voice-orb';
import { useRealtimeVoice } from './use-realtime-voice';
import { useDashboard } from './dashboard-context';

const MODE_LABELS = {
  realtime: 'Live',
  tts: 'Speak replies',
  local: 'Local',
  off: 'Off',
};

const STATE_COPY = {
  idle: 'Standing by',
  connecting: 'Opening line…',
  live: 'Listening',
  ending: 'Ending…',
  error: 'Connection error',
  approval: 'Waiting for approval',
};

function persist(key, value) {
  if (typeof window === 'undefined') return;
  try { window.localStorage.setItem(key, value); } catch {}
}
function load(key, fallback) {
  if (typeof window === 'undefined') return fallback;
  try { return window.localStorage.getItem(key) || fallback; } catch { return fallback; }
}

function VoicePanel() {
  const {
    selectedProjectId,
    spaces: spacesHook,
    state,
    chat,
    setVoiceOpen,
    setConfigTab,
    setConfigOpen,
  } = useDashboard();

  const projectId = selectedProjectId || null;
  const agentId = 'main';
  // Per-space artifact membership derived from artifact.space_id — server is
  // source of truth, no client-side artifactIds tracking.
  const artifactsBySpaceId = (() => {
    const map = new Map();
    for (const a of state?.artifacts || []) {
      const sid = a.space_id || 'default';
      if (!map.has(sid)) map.set(sid, []);
      map.get(sid).push(a);
    }
    return map;
  })();
  const activeSpace = spacesHook.activeSpace ? {
    id: spacesHook.activeSpace.id,
    name: spacesHook.activeSpace.name,
    artifact_titles: (artifactsBySpaceId.get(spacesHook.activeSpace.id) || [])
      .map(a => a.title)
      .filter(Boolean)
      .slice(0, 12),
  } : null;
  const allSpaces = spacesHook.spaces.map(s => ({
    id: s.id,
    name: s.name,
    artifact_count: (artifactsBySpaceId.get(s.id) || []).length,
  }));
  const onClose = () => setVoiceOpen(false);
  const onOpenSettings = () => { setConfigTab('voice'); setConfigOpen(true); };
  const onSpeakLast = async () => {
    for (let i = chat.length - 1; i >= 0; i--) {
      const msg = chat[i];
      if (msg.role === 'agent' && msg.text) return msg.text;
    }
    return null;
  };
  const [providers, setProviders] = useState(null);
  const [mode, setMode] = useState(() => load('madarx:voice-mode', 'realtime'));
  const [ttsProvider, setTtsProvider] = useState(() => load('madarx:tts-provider', 'openai'));
  const [ttsVoice, setTtsVoice] = useState(() => load('madarx:tts-voice', ''));
  const [providerError, setProviderError] = useState(null);
  const [ttsSpeaking, setTtsSpeaking] = useState(false);
  const [collapsed, setCollapsed] = useState(() => load('madarx:voice-collapsed', '0') === '1');

  const realtime = useRealtimeVoice({ projectId, agentId, activeSpace, allSpaces });

  // Capability discovery — lets us hide modes we can't run.
  useEffect(() => {
    let cancelled = false;
    fetch('/api/voice/providers').then(r => r.json()).then(data => {
      if (cancelled) return;
      setProviders(data);
      // Auto-fix mode if the persisted choice isn't supported here.
      if (mode === 'realtime' && !data?.capabilities?.realtime?.openai) {
        setMode(data?.default_mode || 'tts');
      } else if (mode === 'tts' && !data?.default_tts_provider) {
        setMode(data?.default_mode || 'off');
      }
      if (!ttsVoice && data?.default_tts_provider === 'openai') setTtsVoice('alloy');
    }).catch(() => setProviderError('Unable to read voice providers'));
    return () => { cancelled = true; };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Persist
  useEffect(() => persist('madarx:voice-mode', mode), [mode]);
  useEffect(() => persist('madarx:tts-provider', ttsProvider), [ttsProvider]);
  useEffect(() => persist('madarx:tts-voice', ttsVoice || ''), [ttsVoice]);
  useEffect(() => persist('madarx:voice-collapsed', collapsed ? '1' : '0'), [collapsed]);

  // React to settings changes from the config panel (cross-instance sync)
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const onStorage = (e) => {
      if (e.key === 'madarx:voice-mode' && e.newValue) setMode(e.newValue);
      if (e.key === 'madarx:tts-provider' && e.newValue) setTtsProvider(e.newValue);
      if (e.key === 'madarx:tts-voice') setTtsVoice(e.newValue || '');
    };
    window.addEventListener('storage', onStorage);
    return () => window.removeEventListener('storage', onStorage);
  }, []);

  const orbState = useMemo(() => {
    if (realtime.error) return 'error';
    if (realtime.pendingToolCalls.length > 0) return 'approval';
    if (mode === 'realtime') return realtime.state;
    if (ttsSpeaking) return 'live';
    return 'idle';
  }, [mode, realtime.error, realtime.pendingToolCalls.length, realtime.state, ttsSpeaking]);

  const stop = useCallback(() => {
    realtime.stop();
    setTtsSpeaking(false);
  }, [realtime]);

  const start = useCallback(async () => {
    setProviderError(null);
    if (mode === 'realtime') await realtime.start();
  }, [mode, realtime]);

  // Speak last assistant message via TTS (used in tts mode).
  const speakLast = useCallback(async () => {
    setProviderError(null);
    const text = await onSpeakLast?.();
    if (!text) return;
    try {
      setTtsSpeaking(true);
      const res = await fetch('/api/voice/tts', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          text: String(text).slice(0, 3800),
          provider: ttsProvider,
          voice: ttsVoice || undefined,
          project_id: projectId,
        }),
      });
      if (!res.ok) throw new Error(`tts ${res.status}`);
      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const audio = new Audio(url);
      audio.onended = () => { setTtsSpeaking(false); URL.revokeObjectURL(url); };
      audio.onerror = () => { setTtsSpeaking(false); URL.revokeObjectURL(url); };
      await audio.play();
    } catch (err) {
      setTtsSpeaking(false);
      setProviderError(String(err?.message || err));
    }
  }, [onSpeakLast, projectId, ttsProvider, ttsVoice]);

  const liveTranscript = realtime.transcript.slice(-8);
  const caps = providers?.capabilities;
  const realtimeAvailable = Boolean(caps?.realtime?.openai);
  const ttsAvailable = Boolean(caps?.tts?.openai || caps?.tts?.elevenlabs || caps?.tts?.local);
  const localAvailable = Boolean(caps?.stt?.local && caps?.tts?.local);

  // Collapsed: render as a bottom-right bubble. Click to expand. The WebRTC
  // connection stays alive across collapse/expand because the component
  // (and its hooks) remain mounted.
  if (collapsed) {
    const liveBubble = orbState === 'live' || ttsSpeaking;
    return (
      <button
        type="button"
        className={'voice-bubble' + (liveBubble ? ' voice-bubble--live' : '') + (orbState === 'error' ? ' voice-bubble--error' : '')}
        title="Open voice console"
        aria-label="Open voice console"
        onClick={() => setCollapsed(false)}
      >
        <svg width="22" height="22" viewBox="0 0 100 100" fill="none" aria-hidden="true">
          <path d="M 85.32 31.22 A 40 40 0 1 1 78.78 22.20" stroke="currentColor" strokeWidth="6" fill="none" />
          <line x1="21.72" y1="21.72" x2="78.28" y2="78.28" stroke="currentColor" strokeWidth="6" strokeLinecap="round" />
          <line x1="21.72" y1="78.28" x2="78.28" y2="21.72" stroke="currentColor" strokeWidth="6" strokeLinecap="round" />
          <circle cx="82.36" cy="26.49" r="8" fill="#C2452A" />
        </svg>
        {liveBubble && <span className="voice-bubble__pulse" aria-hidden="true" />}
      </button>
    );
  }

  // Minimal Siri/ChatGPT-voice pattern: the orb IS the button. Click to
  // start, click again to stop. Mode switching, voice picking, and
  // provider readiness all live in Config -> Voice; this panel is one
  // luminous orb and a single line of status text.
  const isLive = mode === 'realtime' && realtime.state === 'live';
  const isConnecting = mode === 'realtime' && realtime.state === 'connecting';
  const orbBusy = isConnecting || (mode === 'tts' && ttsSpeaking);

  const onOrbClick = () => {
    if (orbBusy) return;
    if (mode === 'realtime') return isLive ? stop() : start();
    if (mode === 'tts') return speakLast();
    if (mode === 'off' || mode === 'local') { onOpenSettings?.(); return; }
  };

  const statusText = (() => {
    if (realtime.error) return STATE_COPY.error;
    if (realtime.pendingToolCalls.length > 0) return STATE_COPY.approval;
    if (mode === 'realtime') {
      if (realtime.muted && isLive) return 'Muted — tap orb to end';
      if (isLive) return 'Listening — tap orb to end';
      if (isConnecting) return 'Connecting…';
      return 'Tap orb to talk';
    }
    if (mode === 'tts') return ttsSpeaking ? 'Speaking…' : 'Tap orb to speak last reply';
    if (mode === 'off') return 'Voice off — tap to open settings';
    if (mode === 'local') return 'Local lane — tap to open settings';
    return STATE_COPY[orbState] || orbState;
  })();

  return (
    <div className="voice-panel voice-panel--docked" data-state={orbState} role="dialog" aria-label="Voice console">
      <div className="voice-panel__inner">
        {/* Two icon buttons, top-right. White pills so they read on the
            mars surface. Minimize (—) collapses to the bubble; close (x)
            tears down the connection. */}
        <div className="voice-panel__head-actions">
          <button className="voice-panel__icon-btn" type="button" aria-label="Minimize" title="Minimize" onClick={() => setCollapsed(true)}>
            <svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M3 10h8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg>
          </button>
          <button className="voice-panel__icon-btn" type="button" aria-label="Close" title="Close" onClick={() => { stop(); onClose?.(); }}>
            <svg width="13" height="13" viewBox="0 0 14 14" fill="none"><path d="M3 3L11 11M3 11L11 3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg>
          </button>
        </div>

        {/* Orb is the action — click to start, click to stop. */}
        <button
          type="button"
          className={'voice-panel__orb-btn' + (isLive ? ' is-live' : '') + (orbBusy ? ' is-busy' : '')}
          onClick={onOrbClick}
          disabled={orbBusy}
          aria-label={isLive ? 'End conversation' : 'Start conversation'}
        >
          <VoiceOrb state={orbState} audioLevel={realtime.audioLevel} size={148} muted={realtime.muted} />
        </button>

        <div className="voice-panel__status">{statusText}</div>

        {/* Single tiny mute toggle that appears only during a live call.
            Sits below the status line so it never competes with the orb. */}
        {isLive && (
          <button
            type="button"
            className={'voice-panel__mute-btn' + (realtime.muted ? ' is-on' : '')}
            onClick={realtime.toggleMute}
            aria-pressed={realtime.muted}
            title={realtime.muted ? 'Unmute mic' : 'Mute mic'}
          >
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              {realtime.muted ? (
                <>
                  <line x1="1" y1="1" x2="23" y2="23" />
                  <path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" />
                  <path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" />
                </>
              ) : (
                <>
                  <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
                  <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
                </>
              )}
            </svg>
            <span>{realtime.muted ? 'Muted' : 'Mute'}</span>
          </button>
        )}

        {/* One discreet link, bottom-center. Single settings entry-point. */}
        {onOpenSettings && (
          <button type="button" className="voice-panel__settings-link" onClick={onOpenSettings}>
            Voice settings
          </button>
        )}

        {/* Live activity strip — single fixed-height horizontal scroller so
            the panel never resizes when tool calls land or finish. Always
            rendered so the empty / busy transition is just an opacity
            change, not a layout shift. Pending calls render as pulsing
            pills; recent calls as muted status pills. The pulsing pill
            itself communicates "in flight" — no separate count row. */}
        <div
          className={
            'voice-panel__activity' +
            (realtime.pendingToolCalls.length > 0 || (realtime.recentToolCalls && realtime.recentToolCalls.length > 0)
              ? ' is-active'
              : '')
          }
          aria-live="polite"
        >
          <div className="voice-panel__activity-track">
            {realtime.pendingToolCalls.map(c => (
              <span key={c.call_id} className="voice-panel__activity-pill voice-panel__activity-pill--running">
                <span className="voice-panel__activity-dot" aria-hidden />
                {c.name.replace(/_/g, ' ')}
              </span>
            ))}
            {(realtime.recentToolCalls || [])
              .filter(r => !realtime.pendingToolCalls.some(p => p.call_id === r.call_id))
              .slice(-6)
              .map(r => (
                <span
                  key={r.call_id}
                  className={'voice-panel__activity-pill voice-panel__activity-pill--' + r.status}
                  title={`${r.name} · ${Math.round(r.duration_ms / 100) / 10}s`}
                >
                  {r.name.replace(/_/g, ' ')}
                </span>
              ))}
          </div>
        </div>

        {(providerError || realtime.error) && (
          <div className="voice-panel__error" role="alert">{providerError || realtime.error}</div>
        )}

        {providers && !realtimeAvailable && !ttsAvailable && !localAvailable && (
          <div className="voice-panel__hint">No voice keys. Add one in Config → Tools → Secrets.</div>
        )}
      </div>
    </div>
  );
}

export default memo(VoicePanel);
