import { create } from 'zustand';
import { useMessageStore } from './message.ts';
import { useChannelStore } from './channel.ts';
import { useServerStore } from './server.ts';
import { usePresenceStore } from './presence.ts';
import { useTypingStore } from './typing.ts';
import { useVoiceStore } from './voice.ts';
import { useAuthStore } from './auth.ts';
import type { Message } from './message.ts';
import type { Channel } from './channel.ts';
import type { Server } from './server.ts';
import type { UserStatus } from './auth.ts';

type UnknownPayload = Record<string, unknown>;

export interface WsEvent {
  type: string;
  data?: UnknownPayload;
}

interface WebSocketState {
  socket: WebSocket | null;
  connected: boolean;
  connect: () => void;
  disconnect: () => void;
  send: (event: WsEvent) => void;
}

let unreadCount = 0;

if (typeof window !== 'undefined') {
  window.addEventListener('focus', () => {
    unreadCount = 0;
    if (document.title.match(/^\(\d+\)\s/)) {
      document.title = document.title.replace(/^\(\d+\)\s/, '');
    }
  });
}

function getWsHost(): string {
  const { protocol, host } = window.location;
  const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
  return `${wsProtocol}//${host}`;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null;
}

function extractIds(payload: UnknownPayload | undefined): { channel_id: string; message_id: string } | null {
  if (!payload || typeof payload.channel_id !== 'string' || typeof payload.message_id !== 'string') {
    return null;
  }
  return { channel_id: payload.channel_id, message_id: payload.message_id };
}

export const useWebSocketStore = create<WebSocketState>((set, get) => ({
  socket: null,
  connected: false,

  connect: () => {
    const existing = get().socket;
    if (existing && (existing.readyState === WebSocket.OPEN || existing.readyState === WebSocket.CONNECTING)) {
      return;
    }

    const socket = new WebSocket(`${getWsHost()}/ws`);

    let reconnectDelay = 1000;
    const maxReconnectDelay = 30000;
    let reconnectTimeout: number | null = null;

    const scheduleReconnect = () => {
      if (reconnectTimeout) {
        window.clearTimeout(reconnectTimeout);
      }
      reconnectTimeout = window.setTimeout(() => {
        if (!get().connected) {
          get().connect();
        }
      }, reconnectDelay);
      reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay);
    };

    socket.onopen = () => {
      // Cookie-based auth: browser sends session cookie automatically.
      // No need to send a token frame.
    };

    socket.onmessage = (event) => {
      let data: WsEvent;
      try {
        data = JSON.parse(event.data) as WsEvent;
      } catch {
        return;
      }

      if (data.type === 'ready') {
        set({ connected: true });
        reconnectDelay = 1000;
        return;
      }

      if (data.type === 'error') {
        console.error('ws error:', data);
        socket.close();
        return;
      }

      if (data.type === 'ping') {
        get().send({ type: 'pong', data: {} });
        return;
      }

      const addMessage = useMessageStore.getState().addMessage;
      const updateMessage = useMessageStore.getState().updateMessage;
      const removeMessage = useMessageStore.getState().removeMessage;
      const addChannel = useChannelStore.getState().addChannel;
      const updateChannel = useChannelStore.getState().updateChannel;
      const removeChannel = useChannelStore.getState().removeChannel;
      const updateServer = useServerStore.getState().updateServer;

      const payload = data.data || {};

      switch (data.type) {
        case 'MESSAGE_CREATE': {
          if (isRecord(payload)) {
            const msg = payload as unknown as Message;
            addMessage(msg);
            
            // Desktop notification
            const currentUserId = useAuthStore.getState().user?.id;
            if (msg.author_id !== currentUserId && !document.hasFocus()) {
              // Update browser tab title
              unreadCount++;
              if (document.title.match(/^\(\d+\)\s/)) {
                document.title = document.title.replace(/^\(\d+\)\s/, `(${unreadCount}) `);
              } else {
                document.title = `(${unreadCount}) ` + document.title;
              }

              if (Notification.permission === 'granted') {
                const title = msg.author_username ? `New message from ${msg.author_display_name || msg.author_username}` : 'New message';
                const notification = new Notification(title, {
                  body: msg.content,
                  icon: '/icons/icon-192.png',
                });
                notification.onclick = () => {
                  window.focus();
                  notification.close();
                };
              }
            }
          }
          break;
        }
        case 'MESSAGE_UPDATE': {
          if (isRecord(payload)) updateMessage(payload as unknown as Message);
          break;
        }
        case 'MESSAGE_DELETE': {
          const ids = extractIds(payload);
          if (ids) removeMessage(ids.channel_id, ids.message_id);
          break;
        }
        case 'CHANNEL_CREATE': {
          if (isRecord(payload)) addChannel(payload as unknown as Channel);
          break;
        }
        case 'CHANNEL_UPDATE': {
          if (isRecord(payload)) updateChannel(payload as unknown as Channel);
          break;
        }
        case 'CHANNEL_DELETE': {
          const channelId = typeof payload.channel_id === 'string' ? payload.channel_id : null;
          if (channelId) removeChannel(channelId);
          break;
        }
        case 'SERVER_UPDATE': {
          if (isRecord(payload)) updateServer(payload as unknown as Server);
          break;
        }
        case 'PRESENCE_UPDATE': {
          const { user_id, username, status } = payload as Record<string, string>;
          if (user_id && username && status) {
            usePresenceStore.getState().updatePresence(user_id, username, status as UserStatus);
          }
          break;
        }
        case 'TYPING_START': {
          const { channel_id, user_id, username } = payload as Record<string, string>;
          if (channel_id && user_id && username) {
            useTypingStore.getState()._handleTypingEvent({ channel_id, user_id, username });
          }
          break;
        }
        case 'VOICE_WHISPER': {
          // Forwarded from backend — only intended recipient gets this
          const { from_user_id, from_username, message } = payload as Record<string, string>;
          useVoiceStore.getState()._addWhisper({
            fromUserId: from_user_id || '',
            fromUsername: from_username || 'unknown',
            message: message || '',
            timestamp: Date.now(),
          });
          break;
        }
        default:
          break;
      }
    };

    socket.onclose = () => {
      set({ connected: false, socket: null });
      scheduleReconnect();
    };

    socket.onerror = () => {
      socket.close();
    };

    set({ socket });
  },

  disconnect: () => {
    const socket = get().socket;
    if (socket) {
      socket.close();
    }
    set({ socket: null, connected: false });
  },

  send: (event) => {
    const socket = get().socket;
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify(event));
    }
  },
}));
