// Minimal pub/sub for ephemeral UI events that don't belong on the sessionEvents
// bus (sessionEvents is for DB-state changes; this file is for UI-only signals
// like "user clicked send-to-terminal on selected text").
//
// Also exposes a tiny registry of currently-mounted terminal panes so the
// MessageBubble context menu can list them. TerminalPane registers on mount,
// unregisters on unmount. v1.10.4 adds a parallel ChatInput registry used by
// the terminal floating menu's "Send to chat" submenu.

type Listener<T> = (payload: T) => void;

interface EventBus<T> {
  emit(payload: T): void;
  subscribe(listener: Listener<T>): () => void;
}

function createEvent<T>(): EventBus<T> {
  const listeners = new Set<Listener<T>>();
  return {
    emit(payload) {
      for (const l of listeners) {
        try {
          l(payload);
        } catch {
          /* one bad listener shouldn't break others */
        }
      }
    },
    subscribe(listener) {
      listeners.add(listener);
      return () => {
        listeners.delete(listener);
      };
    },
  };
}

export interface SendToTerminalPayload {
  pane_id: string;
  text: string;
}

export const sendToTerminal = createEvent<SendToTerminalPayload>();

// v1.10.4: reverse direction. Terminal floating menu "Send to chat" emits this
// with the target chat's chat_id; ChatInput subscribes and appends to its draft.
export interface SendToChatPayload {
  chat_id: string;
  text: string;
}

export const sendToChat = createEvent<SendToChatPayload>();

export interface TerminalRegistration {
  paneId: string;
  label: string;
  // v1.10.3 kbd-shortcuts: Cmd+` needs to focus the active terminal's xterm
  // input layer. TerminalPane binds this to term.focus().
  focus: () => void;
  // v1.10.4: Cmd+F opens the search bar over the active terminal. Workspace
  // also binds a "Paste" button in the terminal pane header to paste().
  openSearch: () => void;
  paste: () => void;
}

const terminalRegistry = new Map<string, TerminalRegistration>();
const registryListeners = new Set<Listener<void>>();

function notifyRegistry(): void {
  for (const l of registryListeners) {
    try {
      l();
    } catch {
      /* ignore */
    }
  }
}

export const terminalsRegistry = {
  register(
    paneId: string,
    label: string,
    focus: () => void,
    openSearch: () => void,
    paste: () => void,
  ): () => void {
    terminalRegistry.set(paneId, { paneId, label, focus, openSearch, paste });
    notifyRegistry();
    return () => {
      terminalRegistry.delete(paneId);
      notifyRegistry();
    };
  },
  list(): TerminalRegistration[] {
    return Array.from(terminalRegistry.values());
  },
  get(paneId: string): TerminalRegistration | undefined {
    return terminalRegistry.get(paneId);
  },
  subscribe(listener: Listener<void>): () => void {
    registryListeners.add(listener);
    return () => {
      registryListeners.delete(listener);
    };
  },
};

// v1.10.4: parallel registry of mounted ChatInput components so the terminal
// floating menu's "Send to chat" submenu can list open chats. Mirrors
// terminalsRegistry exactly; same subscriber pattern.
export interface ChatInputRegistration {
  chatId: string;
  label: string;
  focus: () => void;
}

const chatInputRegistry = new Map<string, ChatInputRegistration>();
const chatInputListeners = new Set<Listener<void>>();

function notifyChatInputs(): void {
  for (const l of chatInputListeners) {
    try {
      l();
    } catch {
      /* ignore */
    }
  }
}

export const chatInputsRegistry = {
  register(chatId: string, label: string, focus: () => void): () => void {
    chatInputRegistry.set(chatId, { chatId, label, focus });
    notifyChatInputs();
    return () => {
      chatInputRegistry.delete(chatId);
      notifyChatInputs();
    };
  },
  list(): ChatInputRegistration[] {
    return Array.from(chatInputRegistry.values());
  },
  get(chatId: string): ChatInputRegistration | undefined {
    return chatInputRegistry.get(chatId);
  },
  subscribe(listener: Listener<void>): () => void {
    chatInputListeners.add(listener);
    return () => {
      chatInputListeners.delete(listener);
    };
  },
};
