import type { FabricClient } from './fabric-client.js';
import type { IdentityRegistry } from './identity.js';

// OpenClaw tool registration api (loose typing — concrete shape from
// openclaw/plugin-sdk/core at host SDK version).
type ToolApi = {
  registerTool: (def: unknown) => void;
  logger: { info: (m: string) => void; warn: (m: string) => void };
};

type Ctx = { agentId?: string };

const X_BY_KIND: Record<string, string> = {
  chat: 'general',
  work: 'work',
  report: 'report',
  discussion: 'discuss',
};

export function registerFabricTools(
  api: ToolApi,
  client: FabricClient,
  identity: IdentityRegistry,
): void {
  // Resolve the calling agent's Fabric session + a guild's token/endpoint.
  const ctxGuild = async (agentId: string, guildNodeId: string) => {
    const entry = identity.findByAgentId(agentId);
    if (!entry)
      throw new Error(
        `agent ${agentId} not registered — run: AGENT_ID=${agentId} ` +
          `~/.openclaw/bin/fabric-register --api-key <fak_…> (or set ` +
          `channels.fabric.accounts.${agentId}); then restart the gateway`,
      );
    const session = await client.agentLogin(entry.fabricApiKey);
    const guild = session.guilds.find((g) => g.nodeId === guildNodeId);
    const token = session.guildAccessTokens.find((t) => t.guildNodeId === guildNodeId)?.token;
    if (!guild || !token) throw new Error(`agent not a member of guild ${guildNodeId}`);
    return { session, guild, token };
  };

  // NOTE: binding an agent's Fabric API key is intentionally NOT a tool.
  // It's a one-time step done out-of-band via the installed script
  //   ~/.openclaw/bin/fabric-register --api-key <fak_…>   (AGENT_ID or --agent-id)
  // or via static config (channels.fabric.accounts.<agentId>).

  const makeCreate = (kind: 'chat' | 'work' | 'report' | 'discussion') =>
    api.registerTool((ctx: Ctx) => ({
      name: `create-${kind}-channel`,
      description: `Create a Fabric ${kind} channel (x_type=${X_BY_KIND[kind]}).`,
      parameters: {
        type: 'object',
        additionalProperties: false,
        required: ['guildNodeId', 'name'],
        properties: {
          guildNodeId: { type: 'string' },
          name: { type: 'string' },
          isPublic: { type: 'boolean' },
          memberUserIds: { type: 'array', items: { type: 'string' } },
          onDuty: { type: 'string', description: 'required for triage-like flows (unused for these kinds)' },
          listeners: { type: 'array', items: { type: 'string' } },
        },
      },
      execute: async (p: {
        guildNodeId: string;
        name: string;
        isPublic?: boolean;
        memberUserIds?: string[];
      }) => {
        const agentId = ctx.agentId;
        if (!agentId) return { ok: false, error: 'no agent context' };
        const { guild, token } = await ctxGuild(agentId, p.guildNodeId);
        const ch = await client.createChannel(guild.endpoint, token, {
          guildId: p.guildNodeId,
          name: p.name,
          xType: X_BY_KIND[kind],
          isPublic: p.isPublic ?? false,
          memberUserIds: p.memberUserIds ?? [],
        });
        return { ok: true, channelId: ch.id };
      },
    }));

  makeCreate('chat');
  makeCreate('work');
  makeCreate('report');
  makeCreate('discussion');

  // discussion-complete: post a summary then close the channel (Guild
  // /channels/:id/close — history stays readable, new posts -> 409).
  api.registerTool((ctx: Ctx) => ({
    name: 'discussion-complete',
    description: 'Conclude a discussion: post a summary then close the channel.',
    parameters: {
      type: 'object',
      additionalProperties: false,
      required: ['guildNodeId', 'channelId', 'summary'],
      properties: {
        guildNodeId: { type: 'string' },
        channelId: { type: 'string' },
        summary: { type: 'string' },
        callbackChannelId: { type: 'string', description: 'optional channel to also post the summary to' },
      },
    },
    execute: async (p: {
      guildNodeId: string;
      channelId: string;
      summary: string;
      callbackChannelId?: string;
    }) => {
      const agentId = ctx.agentId;
      if (!agentId) return { ok: false, error: 'no agent context' };
      const { session, guild, token } = await ctxGuild(agentId, p.guildNodeId);
      await client.postMessage(guild.endpoint, token, p.channelId, p.summary, session.user.id);
      if (p.callbackChannelId) {
        await client
          .postMessage(guild.endpoint, token, p.callbackChannelId, p.summary, session.user.id)
          .catch(() => undefined);
      }
      await client.closeChannel(guild.endpoint, token, p.channelId);
      return { ok: true, closed: true };
    },
  }));

  // fabric-canvas: share / update / read / close the channel's single
  // pinned canvas document (one tool, four actions). update/close are
  // sharer-only server-side (the guild returns 403 otherwise).
  api.registerTool((ctx: Ctx) => ({
    name: 'fabric-canvas',
    description:
      "Manage a channel's pinned canvas document. action: " +
      "read (current canvas or null) | share (create/replace; you become " +
      'the sharer) | update (edit in place; sharer only) | close (remove; ' +
      'sharer only).',
    parameters: {
      type: 'object',
      additionalProperties: false,
      required: ['action', 'guildNodeId', 'channelId'],
      properties: {
        action: { type: 'string', enum: ['read', 'share', 'update', 'close'] },
        guildNodeId: { type: 'string' },
        channelId: { type: 'string' },
        title: { type: 'string', description: 'share: required; update: optional' },
        format: {
          type: 'string',
          enum: ['md', 'html', 'text'],
          description: 'share: required; update: optional',
        },
        source: {
          type: 'string',
          description: 'document body. share: required; update: optional',
        },
      },
    },
    execute: async (p: {
      action: 'read' | 'share' | 'update' | 'close';
      guildNodeId: string;
      channelId: string;
      title?: string;
      format?: 'md' | 'html' | 'text';
      source?: string;
    }) => {
      const agentId = ctx.agentId;
      if (!agentId) return { ok: false, error: 'no agent context' };
      const { guild, token } = await ctxGuild(agentId, p.guildNodeId);
      const ep = guild.endpoint;
      switch (p.action) {
        case 'read': {
          const canvas = await client.getCanvas(ep, token, p.channelId);
          return { ok: true, canvas };
        }
        case 'share': {
          if (!p.title || !p.format || p.source === undefined) {
            return { ok: false, error: 'share requires title, format, and source' };
          }
          const canvas = await client.shareCanvas(ep, token, p.channelId, {
            title: p.title,
            format: p.format,
            source: p.source,
          });
          return { ok: true, canvas };
        }
        case 'update': {
          const body: Partial<{ title: string; format: 'md' | 'html' | 'text'; source: string }> = {};
          if (p.title !== undefined) body.title = p.title;
          if (p.format !== undefined) body.format = p.format;
          if (p.source !== undefined) body.source = p.source;
          if (Object.keys(body).length === 0) {
            return { ok: false, error: 'update needs at least one of title/format/source' };
          }
          const canvas = await client.updateCanvas(ep, token, p.channelId, body);
          return { ok: true, canvas };
        }
        case 'close': {
          await client.removeCanvas(ep, token, p.channelId);
          return { ok: true, removed: true };
        }
        default:
          return { ok: false, error: `unknown action ${String(p.action)}` };
      }
    },
  }));

  // fabric-channel: channel membership (one tool, three actions).
  api.registerTool((ctx: Ctx) => ({
    name: 'fabric-channel',
    description:
      'Channel membership. action: members (list channel member userIds) | ' +
      'join (this agent joins the channel) | leave (this agent leaves).',
    parameters: {
      type: 'object',
      additionalProperties: false,
      required: ['action', 'guildNodeId', 'channelId'],
      properties: {
        action: { type: 'string', enum: ['members', 'join', 'leave'] },
        guildNodeId: { type: 'string' },
        channelId: { type: 'string' },
      },
    },
    execute: async (p: {
      action: 'members' | 'join' | 'leave';
      guildNodeId: string;
      channelId: string;
    }) => {
      const agentId = ctx.agentId;
      if (!agentId) return { ok: false, error: 'no agent context' };
      const { guild, token } = await ctxGuild(agentId, p.guildNodeId);
      const ep = guild.endpoint;
      switch (p.action) {
        case 'members': {
          const members = await client.channelMembers(ep, token, p.channelId);
          return { ok: true, members };
        }
        case 'join': {
          await client.joinChannel(ep, token, p.channelId);
          return { ok: true, joined: true };
        }
        case 'leave': {
          await client.leaveChannel(ep, token, p.channelId);
          return { ok: true, left: true };
        }
        default:
          return { ok: false, error: `unknown action ${String(p.action)}` };
      }
    },
  }));
}
