import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { DashboardLayout } from "./components/layout/DashboardLayout";
import { MediaPanel } from "./components/media/MediaPanel";
import { MessagesPanel } from "./components/messages/MessagesPanel";
import { ReviewPanel } from "./components/review/ReviewPanel";
import { Tabs, TabsContent } from "./components/ui/tabs";
import { VoicePanel } from "./components/voice/VoicePanel";
import { useDashboardSocket } from "./hooks/useDashboardSocket";
import { mergeMessages, useMessages } from "./hooks/useMessages";
import { useMediaControl } from "./hooks/useMediaControl";
import { useUIState } from "./hooks/useUIState";
import { useVoiceControl } from "./hooks/useVoiceControl";
import type { MessageRecord } from "./types/messages";
import type { DashboardTab } from "./types/ui";
import type { ActiveSpeaker } from "./types/voice";

const SAMPLE_RATE = 24000;
const CHANNELS = 1;

export default function App() {
  const { uiState, setUIState, patchUIState } = useUIState();
  const voice = useVoiceControl();
  const media = useMediaControl();
  const messages = useMessages();
  const [activeSpeakers, setActiveSpeakers] = useState<ActiveSpeaker[]>([]);
  const [levels, setLevels] = useState<number[]>(Array.from({ length: 32 }, () => 0.04));
  const [isListening, setIsListening] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const audioContextListenRef = useRef<AudioContext | null>(null);
  const audioContextTransmitRef = useRef<AudioContext | null>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const processorRef = useRef<ScriptProcessorNode | null>(null);
  const userTimelinesRef = useRef(new Map<number, number>());

  const activeTab = uiState.activeTab || "voice";
  const selectedVoiceGuild = uiState.selectedVoiceGuild || uiState.selectedGuild || "";
  const selectedVoiceChannel = uiState.selectedVoiceChannel || "";
  const selectedTextGuild = uiState.selectedTextGuild || uiState.selectedGuild || "";
  const selectedTextChannel = uiState.selectedTextChannel || "";

  const handleIncomingPcm = useCallback((data: ArrayBuffer) => {
    const headerView = new DataView(data, 0, 4);
    const userIdHash = headerView.getInt32(0, true);
    const audioData = data.slice(4);
    const int16Array = new Int16Array(audioData);
    let sum = 0;
    for (const sample of int16Array) sum += Math.abs(sample / 32768);
    const average = int16Array.length ? sum / int16Array.length : 0;
    setLevels((prev) => prev.map((_, index) => Math.max(0.04, average * (0.5 + Math.sin(index * 0.6 + Date.now() / 140) * 0.35 + 0.65) * 5)));

    const audioContext = audioContextListenRef.current;
    if (!isListening || !audioContext) return;
    const float32Array = new Float32Array(int16Array.length);
    for (let i = 0; i < int16Array.length; i++) float32Array[i] = int16Array[i] / 32768;
    const audioBuffer = audioContext.createBuffer(CHANNELS, float32Array.length / CHANNELS, SAMPLE_RATE);
    audioBuffer.getChannelData(0).set(float32Array);
    const source = audioContext.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(audioContext.destination);
    const currentTime = audioContext.currentTime;
    let nextStart = userTimelinesRef.current.get(userIdHash) || 0;
    if (nextStart < currentTime) nextStart = currentTime + 0.05;
    source.start(nextStart);
    userTimelinesRef.current.set(userIdHash, nextStart + audioBuffer.duration);
  }, [isListening]);

  const socket = useDashboardSocket({
    onUIState: (state) => setUIState((prev) => ({ ...prev, ...state })),
    onUserState: setActiveSpeakers,
    onMessageCreated: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
    onMessageUpdated: (message) => messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, ...message } as MessageRecord : item))),
    onMessageDeleted: (message) => messages.setMessages((prev) => prev.map((item) => (item.id === message.id ? { ...item, type: "deleted" } : item))),
    onMessageAnalyzed: (message) => messages.setMessages((prev) => mergeMessages(prev, [message])),
    onAttachmentUploaded: () => messages.fetchMessages(selectedTextChannel).catch(() => undefined),
    onMediaState: media.setMediaState,
    onPcm: handleIncomingPcm,
  });

  const stopStreamingLocal = useCallback(() => {
    setIsStreaming(false);
    if (processorRef.current) {
      processorRef.current.disconnect();
      processorRef.current = null;
    }
    if (audioContextTransmitRef.current) {
      audioContextTransmitRef.current.close();
      audioContextTransmitRef.current = null;
    }
    if (streamRef.current) {
      for (const track of streamRef.current.getTracks()) track.stop();
      streamRef.current = null;
    }
    setLevels(Array.from({ length: 32 }, () => 0.04));
  }, []);

  const startStreamingLocal = useCallback(async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      streamRef.current = stream;
      setIsStreaming(true);

      const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
      const audioContext = new AudioContextCtor({ sampleRate: SAMPLE_RATE });
      audioContextTransmitRef.current = audioContext;

      const source = audioContext.createMediaStreamSource(stream);
      const processor = audioContext.createScriptProcessor(4096, 1, 1);
      processorRef.current = processor;

      source.connect(processor);
      processor.connect(audioContext.destination);

      processor.onaudioprocess = (event) => {
        if (!socket.socketRef.current || socket.socketRef.current.readyState !== WebSocket.OPEN) return;

        const inputData = event.inputBuffer.getChannelData(0);
        const pcmData = new Int16Array(inputData.length);
        for (let i = 0; i < inputData.length; i++) {
          pcmData[i] = Math.max(-1, Math.min(1, inputData[i])) * 32767;
        }
        socket.socketRef.current.send(pcmData.buffer);

        // Update local levels from mic
        let sum = 0;
        for (let i = 0; i < inputData.length; i++) sum += Math.abs(inputData[i]);
        const average = inputData.length ? sum / inputData.length : 0;
        setLevels((prev) => prev.map((_, index) => Math.max(0.04, average * (0.5 + Math.sin(index * 0.6 + Date.now() / 140) * 0.35 + 0.65) * 5)));
      };
    } catch (err) {
      console.error("Microphone access failed:", err);
      setIsStreaming(false);
      throw err;
    }
  }, [socket.socketRef]);

  const toggleStreaming = useCallback(async () => {
    if (isStreaming) {
      stopStreamingLocal();
      await patchUIState({ isStreaming: false });
    } else {
      await startStreamingLocal();
      await patchUIState({ isStreaming: true });
    }
  }, [isStreaming, startStreamingLocal, stopStreamingLocal, patchUIState]);

  useEffect(() => {
    if (selectedVoiceGuild) {
      voice.loadVoiceChannels(selectedVoiceGuild).catch(() => undefined);
    }
  }, [selectedVoiceGuild]);

  useEffect(() => {
    if (selectedTextGuild) {
      voice.loadTextTargets(selectedTextGuild).catch(() => undefined);
    }
  }, [selectedTextGuild]);

  useEffect(() => {
    if (selectedTextChannel) {
      messages.fetchMessages(selectedTextChannel).catch(() => undefined);
    }
  }, [selectedTextChannel]);

  const toggleListening = useCallback(async () => {
    if (isListening) {
      await audioContextListenRef.current?.suspend();
      userTimelinesRef.current.clear();
      setIsListening(false);
      await patchUIState({ isListening: false });
      return;
    }
    const AudioContextCtor = window.AudioContext || window.webkitAudioContext;
    audioContextListenRef.current ??= new AudioContextCtor({ sampleRate: SAMPLE_RATE });
    await audioContextListenRef.current.resume();
    setIsListening(true);
    await patchUIState({ isListening: true });
  }, [isListening, patchUIState]);

  const tabs = useMemo(() => ["voice", "media", "messages", "review"] as DashboardTab[], []);

  return (
    <DashboardLayout
      activeTab={activeTab}
      wsStatus={socket.status}
      voiceStatus={voice.voiceStatus}
      onTabChange={(tab) => patchUIState({ activeTab: tab })}
    >
      <div className="md:hidden">
        <Tabs value={activeTab} onValueChange={(value) => patchUIState({ activeTab: value as DashboardTab })}>
          <div className="mb-4 grid grid-cols-4 gap-2 rounded-2xl bg-muted p-1">
            {tabs.map((tab) => (
              <button key={tab} className={`rounded-xl px-2 py-2 text-xs font-medium ${activeTab === tab ? "bg-background text-foreground" : "text-muted-foreground"}`} onClick={() => patchUIState({ activeTab: tab })}>
                {tab}
              </button>
            ))}
          </div>
        </Tabs>
      </div>
      <Tabs value={activeTab} onValueChange={(value) => patchUIState({ activeTab: value as DashboardTab })}>
        <TabsContent value="voice">
          <VoicePanel
            guilds={voice.guilds}
            channels={voice.voiceChannels}
            selectedGuild={selectedVoiceGuild}
            selectedChannel={selectedVoiceChannel}
            status={voice.voiceStatus}
            loading={voice.loading}
            activeSpeakers={activeSpeakers}
            levels={levels}
            isListening={isListening}
            isStreaming={isStreaming}
            onGuildChange={(guildId) => patchUIState({ selectedVoiceGuild: guildId, selectedVoiceChannel: "" })}
            onChannelChange={(channelId) => patchUIState({ selectedVoiceChannel: channelId })}
            onJoin={() => voice.joinVoice(selectedVoiceGuild, selectedVoiceChannel)}
            onDisconnect={() => voice.leaveVoice()}
            onListenToggle={toggleListening}
            onStreamingToggle={toggleStreaming}
          />
        </TabsContent>
        <TabsContent value="media">
          <MediaPanel
            state={media.mediaState}
            loading={media.loading}
            onQueueMusic={(source) => media.enqueue(source, "music")}
            onStartScreen={(source) => media.enqueue(source, "screen")}
            onSkip={media.skip}
            onStop={media.stop}
          />
        </TabsContent>
        <TabsContent value="messages">
          <MessagesPanel
            guilds={voice.guilds}
            channels={voice.textChannels}
            selectedGuild={selectedTextGuild}
            selectedChannel={selectedTextChannel}
            messages={messages.messages}
            onGuildChange={(guildId) => patchUIState({ selectedTextGuild: guildId, selectedTextChannel: "" })}
            onChannelChange={(channelId) => patchUIState({ selectedTextChannel: channelId })}
            onReanalyze={messages.reanalyze}
          />
        </TabsContent>
        <TabsContent value="review">
          <ReviewPanel messages={messages.messages} onReanalyze={messages.reanalyze} />
        </TabsContent>
      </Tabs>
    </DashboardLayout>
  );
}
