import type {
  Channel,
  Message,
  PluginEventEnvelope,
  PluginRequirementsSnapshot,
  Room,
  RoomMember,
  RoomPermissions,
  RoomRole,
  RoomRoleAssignment,
  TojuPluginManifest,
  User
} from '../../../../shared-kernel';

export interface TojuPluginDisposable {
  dispose: () => void;
}

export interface TojuPluginActivationContext {
  api: TojuClientPluginApi;
  manifest: TojuPluginManifest;
  pluginId: string;
  subscriptions: TojuPluginDisposable[];
}

export interface TojuClientPluginModule {
  activate?: (context: TojuPluginActivationContext) => Promise<void> | void;
  deactivate?: (context: TojuPluginActivationContext) => Promise<void> | void;
  onPluginDataChanged?: (context: TojuPluginActivationContext, event: unknown) => Promise<void> | void;
  onServerRequirementsChanged?: (context: TojuPluginActivationContext, snapshot: PluginRequirementsSnapshot) => Promise<void> | void;
  ready?: (context: TojuPluginActivationContext) => Promise<void> | void;
}

export interface PluginApiProfileUpdate {
  description?: string;
  displayName: string;
}

export interface PluginApiAvatarUpdate {
  avatarHash: string;
  avatarMime: string;
  avatarUrl: string;
}

export interface PluginApiChannelRequest {
  id?: string;
  name: string;
  position?: number;
}

export interface PluginApiServerSettingsUpdate {
  description?: string;
  isPrivate?: boolean;
  maxUsers?: number;
  name?: string;
  password?: string;
  topic?: string;
}

export interface PluginApiPluginUserRequest {
  avatarUrl?: string;
  displayName: string;
  id?: string;
}

export interface PluginApiMessageAsPluginUserRequest {
  channelId?: string;
  content: string;
  pluginUserId: string;
}

export interface PluginApiAudioClipRequest {
  volume?: number;
  url: string;
}

export interface PluginApiAttachmentImportRequest {
  files: File[];
  messageId: string;
}

export interface PluginApiCustomStreamRequest {
  label?: string;
  stream: MediaStream;
}

export interface PluginApiEventSubscription {
  eventName: string;
  handler: (event: PluginEventEnvelope) => void;
}

export type PluginApiActionSource = 'composerAction' | 'toolbarAction' | 'profileAction' | 'slashCommand' | 'manual';

export interface PluginApiActionContext {
  server: Room | null;
  source: PluginApiActionSource;
  textChannel: Channel | null;
  user: User | null;
  voiceChannel: Channel | null;
}

/**
 * Where a slash command is allowed to appear. `global` commands are available
 * everywhere (chat servers and direct messages); `server` commands only appear
 * while a chat server is the active surface.
 */
export type PluginApiSlashCommandScope = 'global' | 'server';

export type PluginApiSlashCommandOptionType = 'string' | 'number' | 'boolean' | 'rest';

export interface PluginApiSlashCommandOption {
  description?: string;
  name: string;
  required?: boolean;
  type?: PluginApiSlashCommandOptionType;
}

export interface PluginApiSlashCommandContext extends PluginApiActionContext {
  /** Parsed positional/named argument values keyed by option name. */
  args: Record<string, string>;
  /** The invoked command name without the leading slash. */
  command: string;
  /** The raw, unparsed argument string typed after the command name. */
  rawArgs: string;
}

export interface PluginApiSlashCommandContribution {
  description?: string;
  icon?: string;
  name: string;
  options?: PluginApiSlashCommandOption[];
  run: (context: PluginApiSlashCommandContext) => Promise<void> | void;
  scope?: PluginApiSlashCommandScope;
}

export interface PluginApiTypingEvent extends Omit<PluginApiActionContext, 'source'> {
  channelId: string;
  displayName: string;
  isTyping: boolean;
  serverId: string;
  userId: string;
}

export interface PluginApiMessageBusEnvelope {
  channelId?: string;
  eventId: string;
  messages?: Message[];
  payload?: unknown;
  pluginId: string;
  roomId: string;
  sentAt: number;
  sourcePeerId?: string;
  sourceUserId?: string;
  topic: string;
}

export interface PluginApiMessageBusLatestRequest {
  channelId?: string;
  includeDeleted?: boolean;
  limit?: number;
  sinceTimestamp?: number;
  targetPeerId?: string;
  topic?: string;
}

export interface PluginApiMessageBusPublishRequest extends PluginApiMessageBusLatestRequest {
  includeLatestMessages?: boolean;
  includeSelf?: boolean;
  payload?: unknown;
  topic: string;
}

export interface PluginApiMessageBusSubscription {
  channelId?: string;
  handler: (event: PluginApiMessageBusEnvelope) => void;
  latestMessageLimit?: number;
  replayLatest?: boolean;
  topic?: string;
}

export interface PluginApiSettingsPageContribution {
  label: string;
  order?: number;
  render: () => HTMLElement | string;
  settingsKey?: string;
}

export interface PluginApiPageContribution {
  label: string;
  path: string;
  render: () => HTMLElement | string;
}

export interface PluginApiPanelContribution {
  label: string;
  order?: number;
  render: () => HTMLElement | string;
}

export interface PluginApiChannelSectionContribution {
  label: string;
  order?: number;
  type?: 'audio' | 'custom' | 'video';
}

export interface PluginApiActionContribution {
  icon?: string;
  label: string;
  run: (context: PluginApiActionContext) => Promise<void> | void;
}

export interface PluginApiEmbedRendererContribution {
  embedType: string;
  render: (payload: unknown) => HTMLElement | string;
}

export interface PluginApiDomMountRequest {
  element: HTMLElement;
  position?: InsertPosition;
  target: Element | string;
}

export interface PluginApiUiContributionMap {
  appPages: PluginApiPageContribution[];
  channelSections: PluginApiChannelSectionContribution[];
  composerActions: PluginApiActionContribution[];
  embeds: PluginApiEmbedRendererContribution[];
  profileActions: PluginApiActionContribution[];
  settingsPages: PluginApiSettingsPageContribution[];
  sidePanels: PluginApiPanelContribution[];
  slashCommands: PluginApiSlashCommandContribution[];
  toolbarActions: PluginApiActionContribution[];
}

export interface TojuClientPluginApi {
  readonly commands: {
    list: () => PluginApiSlashCommandContribution[];
    register: (id: string, contribution: PluginApiSlashCommandContribution) => TojuPluginDisposable;
  };
  readonly channels: {
    addAudioChannel: (request: PluginApiChannelRequest) => void;
    addTextChannel: (request: PluginApiChannelRequest) => void;
    addVideoChannel: (request: PluginApiChannelRequest) => void;
    list: () => Channel[];
    remove: (channelId: string) => void;
    rename: (channelId: string, name: string) => void;
    select: (channelId: string) => void;
  };
  readonly events: {
    publishP2p: (eventName: string, payload: unknown) => void;
    publishServer: (eventName: string, payload: unknown) => void;
    subscribeP2p: (subscription: PluginApiEventSubscription) => TojuPluginDisposable;
    subscribeServer: (subscription: PluginApiEventSubscription) => TojuPluginDisposable;
  };
  readonly logger: {
    debug: (message: string, data?: unknown) => void;
    error: (message: string, data?: unknown) => void;
    info: (message: string, data?: unknown) => void;
    warn: (message: string, data?: unknown) => void;
  };
  readonly context: {
    getCurrent: () => PluginApiActionContext;
  };
  readonly clientData: {
    read: (key: string) => Promise<unknown>;
    remove: (key: string) => Promise<void>;
    write: (key: string, value: unknown) => Promise<void>;
  };
  readonly attachments: {
    import: (request: PluginApiAttachmentImportRequest) => Promise<void>;
  };
  readonly media: {
    addCustomAudioStream: (request: PluginApiCustomStreamRequest) => Promise<void>;
    addCustomVideoStream: (request: PluginApiCustomStreamRequest) => Promise<void>;
    playAudioClip: (request: PluginApiAudioClipRequest) => Promise<void>;
    setInputVolume: (volume: number) => void;
    setOutputVolume: (volume: number) => void;
  };
  readonly messages: {
    delete: (messageId: string) => void;
    edit: (messageId: string, content: string) => void;
    moderateDelete: (messageId: string) => void;
    readCurrent: () => Message[];
    send: (content: string, channelId?: string) => Message;
    sendAsPluginUser: (request: PluginApiMessageAsPluginUserRequest) => void;
    import: (messages: Message[]) => Promise<void>;
    setTyping: (isTyping: boolean, channelId?: string) => void;
    subscribeTyping: (handler: (event: PluginApiTypingEvent) => void) => TojuPluginDisposable;
    sync: (messages: Message[]) => void;
  };
  readonly messageBus: {
    publish: (request: PluginApiMessageBusPublishRequest) => PluginApiMessageBusEnvelope;
    sendLatestMessages: (request?: PluginApiMessageBusLatestRequest) => PluginApiMessageBusEnvelope;
    subscribe: (subscription: PluginApiMessageBusSubscription) => TojuPluginDisposable;
  };
  readonly p2p: {
    broadcastData: (eventName: string, payload: unknown) => void;
    connectedPeers: () => string[];
    sendData: (peerId: string, eventName: string, payload: unknown) => void;
  };
  readonly profile: {
    getCurrent: () => User | null;
    update: (profile: PluginApiProfileUpdate) => void;
    updateAvatar: (avatar: PluginApiAvatarUpdate) => void;
  };
  readonly roles: {
    list: () => RoomRole[];
    setAssignments: (assignments: RoomRoleAssignment[]) => void;
  };
  readonly server: {
    getCurrent: () => Room | null;
    registerPluginUser: (request: PluginApiPluginUserRequest) => string;
    updateIcon: (icon: string) => Promise<void>;
    updatePermissions: (permissions: Partial<RoomPermissions>) => void;
    updateSettings: (settings: PluginApiServerSettingsUpdate) => void;
  };
  readonly serverData: {
    read: (key: string) => Promise<unknown>;
    remove: (key: string) => Promise<void>;
    write: (key: string, value: unknown) => Promise<void>;
  };
  readonly storage: {
    get: (key: string) => unknown;
    remove: (key: string) => void;
    set: (key: string, value: unknown) => void;
  };
  readonly ui: {
    registerAppPage: (id: string, contribution: PluginApiPageContribution) => TojuPluginDisposable;
    registerChannelSection: (id: string, contribution: PluginApiChannelSectionContribution) => TojuPluginDisposable;
    registerComposerAction: (id: string, contribution: PluginApiActionContribution) => TojuPluginDisposable;
    registerEmbedRenderer: (id: string, contribution: PluginApiEmbedRendererContribution) => TojuPluginDisposable;
    mountElement: (id: string, request: PluginApiDomMountRequest) => TojuPluginDisposable;
    registerProfileAction: (id: string, contribution: PluginApiActionContribution) => TojuPluginDisposable;
    registerSettingsPage: (id: string, contribution: PluginApiSettingsPageContribution) => TojuPluginDisposable;
    registerSidePanel: (id: string, contribution: PluginApiPanelContribution) => TojuPluginDisposable;
    registerToolbarAction: (id: string, contribution: PluginApiActionContribution) => TojuPluginDisposable;
  };
  readonly users: {
    ban: (userId: string, reason?: string) => void;
    getCurrent: () => User | null;
    kick: (userId: string) => void;
    list: () => User[];
    readMembers: () => RoomMember[];
    setRole: (userId: string, role: User['role']) => void;
  };
}
