/**
 * composePlugins — chain N plugins into one.
 *
 * The router only accepts a single plugin per project session. When we want
 * orthogonal plugin behaviors (e.g. the existing `default` proxymodel PLUS
 * the agents plugin's virtual-server registration), we compose them into a
 * single facade that fans each hook out to all parents in order. This is
 * a generalization of what `createDefaultPlugin` does manually for two
 * fixed parents.
 *
 * Hook semantics:
 *   - onSessionCreate / onSessionDestroy: every plugin's hook runs in order.
 *   - onInitialize: instructions from every plugin are concatenated (in order,
 *     blank-line separated) so orthogonal plugins can each contribute a banner.
 *   - onToolsList / onResourcesList / onPromptsList: results pipeline through
 *     the plugins, each transforming the previous step's output.
 *   - onToolCallBefore / onResourceRead / onPromptGet: first non-null wins
 *     (an interceptor short-circuits the chain).
 *   - onToolCallAfter: pipeline — each plugin can transform the response.
 *
 * For chat-style plugins (gate, content-pipeline, agents), this is what you
 * want: agents registers virtual servers in onSessionCreate without
 * conflicting with gate's onToolCallBefore interceptors.
 */
import type { ProxyModelPlugin } from '../plugin.js';

export function composePlugins(plugins: ProxyModelPlugin[]): ProxyModelPlugin {
  if (plugins.length === 0) {
    throw new Error('composePlugins requires at least one plugin');
  }
  if (plugins.length === 1) return plugins[0]!;

  const out: ProxyModelPlugin = {
    name: plugins.map((p) => p.name).join('+'),
    description: 'Composed: ' + plugins.map((p) => p.name).join(', '),
  };

  if (plugins.some((p) => p.onSessionCreate)) {
    out.onSessionCreate = async (ctx) => {
      for (const p of plugins) {
        if (p.onSessionCreate) await p.onSessionCreate(ctx);
      }
    };
  }
  if (plugins.some((p) => p.onSessionDestroy)) {
    out.onSessionDestroy = async (ctx) => {
      for (const p of plugins) {
        if (p.onSessionDestroy) await p.onSessionDestroy(ctx);
      }
    };
  }
  if (plugins.some((p) => p.onInitialize)) {
    out.onInitialize = async (request, ctx) => {
      const parts: string[] = [];
      for (const p of plugins) {
        if (p.onInitialize) {
          const res = await p.onInitialize(request, ctx);
          const instr = res?.instructions?.trim();
          if (instr) parts.push(instr);
        }
      }
      return parts.length > 0 ? { instructions: parts.join('\n\n') } : null;
    };
  }
  if (plugins.some((p) => p.onToolsList)) {
    out.onToolsList = async (tools, ctx) => {
      let acc = tools;
      for (const p of plugins) {
        if (p.onToolsList) acc = await p.onToolsList(acc, ctx);
      }
      return acc;
    };
  }
  if (plugins.some((p) => p.onToolCallBefore)) {
    out.onToolCallBefore = async (toolName, args, request, ctx) => {
      for (const p of plugins) {
        if (p.onToolCallBefore) {
          const intercepted = await p.onToolCallBefore(toolName, args, request, ctx);
          if (intercepted !== null) return intercepted;
        }
      }
      return null;
    };
  }
  if (plugins.some((p) => p.onToolCallAfter)) {
    out.onToolCallAfter = async (toolName, args, response, ctx) => {
      let acc = response;
      for (const p of plugins) {
        if (p.onToolCallAfter) acc = await p.onToolCallAfter(toolName, args, acc, ctx);
      }
      return acc;
    };
  }
  if (plugins.some((p) => p.onResourcesList)) {
    out.onResourcesList = async (resources, ctx) => {
      let acc = resources;
      for (const p of plugins) {
        if (p.onResourcesList) acc = await p.onResourcesList(acc, ctx);
      }
      return acc;
    };
  }
  if (plugins.some((p) => p.onResourceRead)) {
    out.onResourceRead = async (uri, request, ctx) => {
      for (const p of plugins) {
        if (p.onResourceRead) {
          const res = await p.onResourceRead(uri, request, ctx);
          if (res !== null) return res;
        }
      }
      return null;
    };
  }
  if (plugins.some((p) => p.onPromptsList)) {
    out.onPromptsList = async (prompts, ctx) => {
      let acc = prompts;
      for (const p of plugins) {
        if (p.onPromptsList) acc = await p.onPromptsList(acc, ctx);
      }
      return acc;
    };
  }
  if (plugins.some((p) => p.onPromptGet)) {
    out.onPromptGet = async (name, request, ctx) => {
      for (const p of plugins) {
        if (p.onPromptGet) {
          const res = await p.onPromptGet(name, request, ctx);
          if (res !== null) return res;
        }
      }
      return null;
    };
  }
  return out;
}
