import type { PluginCapabilityId } from '../../../../shared-kernel';

/**
 * Canonical registry of every public `TojuClientPluginApi` namespace and method.
 * Keep this list aligned with `plugin-api.models.ts` when the contract changes.
 */
export const PLUGIN_CLIENT_API_SURFACE = {
  attachments: ['import'],
  channels: [
    'addAudioChannel',
    'addTextChannel',
    'addVideoChannel',
    'list',
    'remove',
    'rename',
    'select'
  ],
  clientData: [
    'read',
    'remove',
    'write'
  ],
  commands: ['list', 'register'],
  context: ['getCurrent'],
  events: [
    'publishP2p',
    'publishServer',
    'subscribeP2p',
    'subscribeServer'
  ],
  logger: [
    'debug',
    'error',
    'info',
    'warn'
  ],
  media: [
    'addCustomAudioStream',
    'addCustomVideoStream',
    'playAudioClip',
    'setInputVolume',
    'setOutputVolume'
  ],
  messageBus: [
    'publish',
    'sendLatestMessages',
    'subscribe'
  ],
  messages: [
    'delete',
    'edit',
    'import',
    'moderateDelete',
    'readCurrent',
    'send',
    'sendAsPluginUser',
    'setTyping',
    'subscribeTyping',
    'sync'
  ],
  p2p: [
    'broadcastData',
    'connectedPeers',
    'sendData'
  ],
  profile: [
    'getCurrent',
    'update',
    'updateAvatar'
  ],
  roles: ['list', 'setAssignments'],
  server: [
    'getCurrent',
    'registerPluginUser',
    'updateIcon',
    'updatePermissions',
    'updateSettings'
  ],
  serverData: [
    'read',
    'remove',
    'write'
  ],
  storage: [
    'get',
    'remove',
    'set'
  ],
  ui: [
    'mountElement',
    'registerAppPage',
    'registerChannelSection',
    'registerComposerAction',
    'registerEmbedRenderer',
    'registerProfileAction',
    'registerSettingsPage',
    'registerSidePanel',
    'registerToolbarAction'
  ],
  users: [
    'ban',
    'getCurrent',
    'kick',
    'list',
    'readMembers',
    'setRole'
  ]
} as const;

export type PluginClientApiNamespace = keyof typeof PLUGIN_CLIENT_API_SURFACE;
export type PluginClientApiMethodPath = {
  [Namespace in PluginClientApiNamespace]: `${Namespace & string}.${typeof PLUGIN_CLIENT_API_SURFACE[Namespace][number]}`;
}[PluginClientApiNamespace];

/**
 * Capability required before a method may run. Methods omitted here are always available.
 */
export const PLUGIN_CLIENT_API_CAPABILITY_REQUIREMENTS: Partial<Record<PluginClientApiMethodPath, PluginCapabilityId>> = {
  'attachments.import': 'messages.sync',
  'channels.addAudioChannel': 'channels.manage',
  'channels.addTextChannel': 'channels.manage',
  'channels.addVideoChannel': 'channels.manage',
  'channels.list': 'channels.read',
  'channels.remove': 'channels.manage',
  'channels.rename': 'channels.manage',
  'channels.select': 'channels.read',
  'clientData.read': 'storage.local',
  'clientData.remove': 'storage.local',
  'clientData.write': 'storage.local',
  'commands.list': 'ui.commands',
  'commands.register': 'ui.commands',
  'events.publishP2p': 'events.p2p.publish',
  'events.publishServer': 'events.server.publish',
  'events.subscribeP2p': 'events.p2p.subscribe',
  'events.subscribeServer': 'events.server.subscribe',
  'media.addCustomAudioStream': 'media.addAudioStream',
  'media.addCustomVideoStream': 'media.addVideoStream',
  'media.playAudioClip': 'media.playAudio',
  'media.setInputVolume': 'audio.volume',
  'media.setOutputVolume': 'audio.volume',
  'messageBus.publish': 'events.p2p.publish',
  'messageBus.sendLatestMessages': 'events.p2p.publish',
  'messageBus.subscribe': 'events.p2p.subscribe',
  'messages.delete': 'messages.deleteOwn',
  'messages.edit': 'messages.editOwn',
  'messages.import': 'messages.sync',
  'messages.moderateDelete': 'messages.moderate',
  'messages.readCurrent': 'messages.read',
  'messages.send': 'messages.send',
  'messages.sendAsPluginUser': 'messages.send',
  'messages.setTyping': 'messages.send',
  'messages.subscribeTyping': 'messages.read',
  'messages.sync': 'messages.sync',
  'p2p.broadcastData': 'p2p.data',
  'p2p.connectedPeers': 'p2p.data',
  'p2p.sendData': 'p2p.data',
  'profile.getCurrent': 'profile.read',
  'profile.update': 'profile.write',
  'profile.updateAvatar': 'profile.write',
  'roles.list': 'roles.read',
  'roles.setAssignments': 'roles.manage',
  'server.getCurrent': 'server.read',
  'server.registerPluginUser': 'users.manage',
  'server.updateIcon': 'server.manage',
  'server.updatePermissions': 'server.manage',
  'server.updateSettings': 'server.manage',
  'serverData.read': 'storage.serverData.read',
  'serverData.remove': 'storage.serverData.write',
  'serverData.write': 'storage.serverData.write',
  'storage.get': 'storage.local',
  'storage.remove': 'storage.local',
  'storage.set': 'storage.local',
  'ui.mountElement': 'ui.dom',
  'ui.registerAppPage': 'ui.pages',
  'ui.registerChannelSection': 'ui.channelsSection',
  'ui.registerComposerAction': 'ui.pages',
  'ui.registerEmbedRenderer': 'ui.embeds',
  'ui.registerProfileAction': 'ui.pages',
  'ui.registerSettingsPage': 'ui.settings',
  'ui.registerSidePanel': 'ui.sidePanel',
  'ui.registerToolbarAction': 'ui.pages',
  'users.ban': 'users.manage',
  'users.getCurrent': 'users.read',
  'users.kick': 'users.manage',
  'users.list': 'users.read',
  'users.readMembers': 'users.read',
  'users.setRole': 'roles.manage'
};

export function collectPluginApiMethodPaths(
  surface: typeof PLUGIN_CLIENT_API_SURFACE = PLUGIN_CLIENT_API_SURFACE
): PluginClientApiMethodPath[] {
  return Object.entries(surface).flatMap(([namespace, methods]) =>
    methods.map((method) => `${namespace}.${method}` as PluginClientApiMethodPath)
  );
}

export function getPluginApiMethod(
  api: Record<string, Record<string, unknown>>,
  path: PluginClientApiMethodPath
): unknown {
  const [namespace, method] = path.split('.') as [PluginClientApiNamespace, string];

  return api[namespace]?.[method];
}

export function assertPluginApiSurfaceImplemented(
  api: Record<string, Record<string, unknown>>,
  surface: typeof PLUGIN_CLIENT_API_SURFACE = PLUGIN_CLIENT_API_SURFACE
): void {
  const missing: string[] = [];

  for (const path of collectPluginApiMethodPaths(surface)) {
    const method = getPluginApiMethod(api, path);

    if (typeof method !== 'function') {
      missing.push(path);
    }
  }

  if (missing.length > 0) {
    throw new Error(`Plugin API surface is incomplete: ${missing.join(', ')}`);
  }
}

export function collectRequiredPluginApiCapabilities(): PluginCapabilityId[] {
  return Array.from(new Set(Object.values(PLUGIN_CLIENT_API_CAPABILITY_REQUIREMENTS))).sort();
}
