import { create } from "zustand";
import { api } from "../lib/api.ts";

export interface MessageEmbed {
  id?: string;
  url: string;
  title?: string;
  description?: string;
  image_url?: string;
  site_name?: string;
}

export interface Reaction {
  emoji: string;
  count: number;
  users: string[];
}

export interface PollOption {
  id: string;
  text: string;
  position: number;
  votes: number;
  voters: string[];
}

export interface Poll {
  id: string;
  message_id: string;
  question: string;
  options: PollOption[];
  created_at: string;
}

export interface Message {
  id: string;
  channel_id: string;
  author_id: string;
  author_username: string;
  author_display_name: string | null;
  content: string;
  reply_to?: string | null;
  embeds?: MessageEmbed[];
  pinned: boolean;
  reactions?: Reaction[];
  poll?: Poll | null;
  created_at: string;
  edited_at: string | null;
}

export interface SearchResultMessage extends Message {
  channel_name?: string;
}

export interface MessageState {
  messagesByChannel: Record<string, Message[]>;
  pinnedMessagesByChannel: Record<string, Message[]>;
  searchResultsByChannel: Record<string, SearchResultMessage[]>;
  selectedMessageIds: Record<string, Set<string>>; // ponytail: per-channel bulk selection
  isLoading: boolean;
  isLoadingOlder: boolean;
  hasMoreByChannel: Record<string, boolean>;
  error: string | null;
  fetchMessages: (channelId: string, before?: string) => Promise<void>;
  fetchOlderMessages: (channelId: string) => Promise<void>;
  sendMessage: (channelId: string, content: string, replyTo?: string) => Promise<Message>;
  searchMessages: (channelId: string, query: string) => Promise<SearchResultMessage[]>;
  pinMessage: (channelId: string, messageId: string) => Promise<void>;
  unpinMessage: (channelId: string, messageId: string) => Promise<void>;
  fetchPinnedMessages: (channelId: string) => Promise<Message[]>;
  addMessage: (message: Message) => void;
  updateMessage: (message: Message) => void;
  removeMessage: (channelId: string, messageId: string) => void;
  addReaction: (channelId: string, messageId: string, emoji: string, userId: string) => void;
  removeReaction: (channelId: string, messageId: string, emoji: string, userId: string) => void;
  bulkDeleteMessages: (channelId: string, messageIds: string[]) => Promise<{ deleted: number }>;
  toggleSelectedMessage: (channelId: string, messageId: string) => void;
  clearSelectedMessages: (channelId: string) => void;
  createPoll: (channelId: string, question: string, options: string[]) => Promise<Poll>;
  votePoll: (pollId: string, optionId: string) => Promise<void>;
  updatePoll: (channelId: string, poll: Poll) => void;
}

export const useMessageStore = create<MessageState>((set, get) => ({
  messagesByChannel: {},
  pinnedMessagesByChannel: {},
  searchResultsByChannel: {},
  selectedMessageIds: {},
  isLoading: false,
  isLoadingOlder: false,
  hasMoreByChannel: {},
  error: null,

  fetchMessages: async (channelId, before) => {
    set({ isLoading: true, error: null });
    try {
      const params = before ? `?before=${encodeURIComponent(before)}` : "";
      const messages = await api.get<Message[]>(
        `/channels/${channelId}/messages${params}`,
      );
      const list = Array.isArray(messages) ? messages : [];
      set((state) => ({
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: list,
        },
        hasMoreByChannel: {
          ...state.hasMoreByChannel,
          [channelId]: list.length >= 50,
        },
        isLoading: false,
      }));
    } catch (error) {
      set({
        isLoading: false,
        error:
          error instanceof Error
            ? error.message
            : "Failed to fetch messages",
      });
    }
  },

  fetchOlderMessages: async (channelId) => {
    const state = get();
    if (state.isLoadingOlder || state.hasMoreByChannel[channelId] === false) return;
    const existing = state.messagesByChannel[channelId] || [];
    if (existing.length === 0) return;
    const oldestId = existing[0].id;
    set({ isLoadingOlder: true });
    try {
      const older = await api.get<Message[]>(
        `/channels/${channelId}/messages?before=${encodeURIComponent(oldestId)}`,
      );
      const list = Array.isArray(older) ? older : [];
      set((state) => ({
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: [...list, ...existing],
        },
        hasMoreByChannel: {
          ...state.hasMoreByChannel,
          [channelId]: list.length >= 50,
        },
        isLoadingOlder: false,
      }));
    } catch {
      set({ isLoadingOlder: false });
    }
  },

  searchMessages: async (channelId, query) => {
    const results = await api.get<SearchResultMessage[]>(
      `/channels/${channelId}/messages/search?q=${encodeURIComponent(query)}`,
    );
    set((state) => ({
      searchResultsByChannel: {
        ...state.searchResultsByChannel,
        [channelId]: Array.isArray(results) ? results : [],
      },
    }));
    return Array.isArray(results) ? results : [];
  },

  sendMessage: async (channelId, content, replyTo) => {
    const body: { content: string; reply_to?: string } = { content };
    if (replyTo) body.reply_to = replyTo;
    // ponytail: don't append here — WS MESSAGE_CREATE broadcast is the single source of truth.
    // Appending in both places caused double messages when the WS event arrived before the HTTP response.
    const message = await api.post<Message>(
      `/channels/${channelId}/messages`,
      body,
    );
    return message;
  },

  pinMessage: async (channelId, messageId) => {
    const updated = await api.put<Message>(`/channels/${channelId}/messages/${messageId}/pin`, {});
    get().updateMessage(updated);
  },

  unpinMessage: async (channelId, messageId) => {
    const updated = await api.delete<Message>(`/channels/${channelId}/messages/${messageId}/pin`);
    get().updateMessage(updated);
  },

  fetchPinnedMessages: async (channelId) => {
    const pinned = await api.get<Message[]>(`/channels/${channelId}/messages/pinned`);
    const list = Array.isArray(pinned) ? pinned : [];
    set((state) => ({
      pinnedMessagesByChannel: {
        ...state.pinnedMessagesByChannel,
        [channelId]: list,
      },
    }));
    return list;
  },

  addMessage: (message) =>
    set((state) => {
      const list = state.messagesByChannel[message.channel_id] || [];
      if (list.some((m) => m.id === message.id)) {
        return state;
      }
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [message.channel_id]: [...list, message],
        },
      };
    }),

  updateMessage: (message) =>
    set((state) => {
      const list = state.messagesByChannel[message.channel_id] || [];
      const updatedList = list.map((m) =>
        m.id === message.id ? message : m,
      );

      const pinnedList = state.pinnedMessagesByChannel[message.channel_id] || [];
      let updatedPinned = [...pinnedList];
      if (message.pinned) {
        if (!pinnedList.some((m) => m.id === message.id)) {
          updatedPinned = [message, ...pinnedList].sort(
            (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
          );
        } else {
          updatedPinned = pinnedList.map((m) => (m.id === message.id ? message : m));
        }
      } else {
        updatedPinned = pinnedList.filter((m) => m.id !== message.id);
      }
      if (updatedPinned.length > 5) {
        updatedPinned = updatedPinned.slice(0, 5);
      }

      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [message.channel_id]: updatedList,
        },
        pinnedMessagesByChannel: {
          ...state.pinnedMessagesByChannel,
          [message.channel_id]: updatedPinned,
        },
      };
    }),

  removeMessage: (channelId, messageId) =>
    set((state) => {
      const list = state.messagesByChannel[channelId] || [];
      const pinnedList = state.pinnedMessagesByChannel[channelId] || [];
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: list.filter((m) => m.id !== messageId),
        },
        pinnedMessagesByChannel: {
          ...state.pinnedMessagesByChannel,
          [channelId]: pinnedList.filter((m) => m.id !== messageId),
        },
      };
    }),

  addReaction: (channelId, messageId, emoji, userId) =>
    set((state) => {
      const list = state.messagesByChannel[channelId] || [];
      const updatedList = list.map((m) => {
        if (m.id !== messageId) return m;
        const reactions = m.reactions ? [...m.reactions] : [];
        const existing = reactions.find((r) => r.emoji === emoji);
        if (existing) {
          if (!existing.users.includes(userId)) {
            existing.users = [...existing.users, userId];
            existing.count = existing.users.length;
          }
        } else {
          reactions.push({ emoji, count: 1, users: [userId] });
        }
        return { ...m, reactions };
      });
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: updatedList,
        },
      };
    }),

  removeReaction: (channelId, messageId, emoji, userId) =>
    set((state) => {
      const list = state.messagesByChannel[channelId] || [];
      const updatedList = list.map((m) => {
        if (m.id !== messageId) return m;
        if (!m.reactions) return m;
        const reactions = m.reactions
          .map((r) => {
            if (r.emoji !== emoji) return r;
            const users = r.users.filter((id) => id !== userId);
            return { ...r, users, count: users.length };
          })
          .filter((r) => r.count > 0);
        return { ...m, reactions };
      });
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: updatedList,
        },
      };
    }),

  bulkDeleteMessages: async (channelId, messageIds) => {
    const res = await api.post<{ deleted: number }>(
      `/channels/${channelId}/messages/bulk-delete`,
      { messages: messageIds },
    );
    set((state) => {
      const list = state.messagesByChannel[channelId] || [];
      const ids = new Set(messageIds);
      const selected = { ...state.selectedMessageIds };
      delete selected[channelId];
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: list.filter((m) => !ids.has(m.id)),
        },
        selectedMessageIds: selected,
      };
    });
    return res;
  },

  toggleSelectedMessage: (channelId, messageId) =>
    set((state) => {
      const current = state.selectedMessageIds[channelId] || new Set<string>();
      const next = new Set(current);
      if (next.has(messageId)) {
        next.delete(messageId);
      } else {
        next.add(messageId);
      }
      return {
        selectedMessageIds: {
          ...state.selectedMessageIds,
          [channelId]: next,
        },
      };
    }),

  clearSelectedMessages: (channelId) =>
    set((state) => {
      const next = { ...state.selectedMessageIds };
      delete next[channelId];
      return { selectedMessageIds: next };
    }),

  createPoll: async (channelId, question, options) => {
    const resp = await api.post<Poll>("/polls", {
      channel_id: channelId,
      question,
      options,
    });
    return resp;
  },

  votePoll: async (pollId, optionId) => {
    await api.post(`/polls/${pollId}/vote`, { option_id: optionId });
  },

  updatePoll: (channelId, poll) =>
    set((state) => {
      const messages = state.messagesByChannel[channelId];
      if (!messages) return state;
      const updated = messages.map((m) =>
        m.poll?.id === poll.id ? { ...m, poll } : m,
      );
      return {
        messagesByChannel: {
          ...state.messagesByChannel,
          [channelId]: updated,
        },
      };
    }),
}));
