import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type { ImageContent, TextContent } from "@mariozechner/pi-ai";
import {
  extractToolResultMediaArtifact,
  filterToolResultMediaUrls,
  isMessagingTool,
  isMessagingToolSendAction,
  type AnyAgentTool,
  type MessagingToolSend,
} from "openclaw/plugin-sdk/agent-harness";
import {
  type CodexDynamicToolCallOutputContentItem,
  type CodexDynamicToolCallParams,
  type CodexDynamicToolCallResponse,
  type CodexDynamicToolSpec,
  type JsonValue,
} from "./protocol.js";

export type CodexDynamicToolBridge = {
  specs: CodexDynamicToolSpec[];
  handleToolCall: (params: CodexDynamicToolCallParams) => Promise<CodexDynamicToolCallResponse>;
  telemetry: {
    didSendViaMessagingTool: boolean;
    messagingToolSentTexts: string[];
    messagingToolSentMediaUrls: string[];
    messagingToolSentTargets: MessagingToolSend[];
    toolMediaUrls: string[];
    toolAudioAsVoice: boolean;
    successfulCronAdds?: number;
  };
};

export function createCodexDynamicToolBridge(params: {
  tools: AnyAgentTool[];
  signal: AbortSignal;
}): CodexDynamicToolBridge {
  const toolMap = new Map(params.tools.map((tool) => [tool.name, tool]));
  const telemetry: CodexDynamicToolBridge["telemetry"] = {
    didSendViaMessagingTool: false,
    messagingToolSentTexts: [],
    messagingToolSentMediaUrls: [],
    messagingToolSentTargets: [],
    toolMediaUrls: [],
    toolAudioAsVoice: false,
  };

  return {
    specs: params.tools.map((tool) => ({
      name: tool.name,
      description: tool.description,
      inputSchema: toJsonValue(tool.parameters),
    })),
    telemetry,
    handleToolCall: async (call) => {
      const tool = toolMap.get(call.tool);
      if (!tool) {
        return {
          contentItems: [{ type: "inputText", text: `Unknown OpenClaw tool: ${call.tool}` }],
          success: false,
        };
      }
      const args = jsonObjectToRecord(call.arguments);
      try {
        const preparedArgs = tool.prepareArguments ? tool.prepareArguments(args) : args;
        const result = await tool.execute(call.callId, preparedArgs, params.signal);
        collectToolTelemetry({
          toolName: tool.name,
          args,
          result,
          telemetry,
          isError: false,
        });
        return {
          contentItems: result.content.flatMap(convertToolContent),
          success: true,
        };
      } catch (error) {
        collectToolTelemetry({
          toolName: tool.name,
          args,
          result: undefined,
          telemetry,
          isError: true,
        });
        return {
          contentItems: [
            {
              type: "inputText",
              text: error instanceof Error ? error.message : String(error),
            },
          ],
          success: false,
        };
      }
    },
  };
}

function collectToolTelemetry(params: {
  toolName: string;
  args: Record<string, unknown>;
  result: AgentToolResult<unknown> | undefined;
  telemetry: CodexDynamicToolBridge["telemetry"];
  isError: boolean;
}): void {
  if (params.isError) {
    return;
  }
  if (!params.isError && params.toolName === "cron" && isCronAddAction(params.args)) {
    params.telemetry.successfulCronAdds = (params.telemetry.successfulCronAdds ?? 0) + 1;
  }
  if (!params.isError && params.result) {
    const media = extractToolResultMediaArtifact(params.result);
    if (media) {
      const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.result);
      const seen = new Set(params.telemetry.toolMediaUrls);
      for (const mediaUrl of mediaUrls) {
        if (!seen.has(mediaUrl)) {
          seen.add(mediaUrl);
          params.telemetry.toolMediaUrls.push(mediaUrl);
        }
      }
      if (media.audioAsVoice) {
        params.telemetry.toolAudioAsVoice = true;
      }
    }
  }
  if (
    !isMessagingTool(params.toolName) ||
    !isMessagingToolSendAction(params.toolName, params.args)
  ) {
    return;
  }
  params.telemetry.didSendViaMessagingTool = true;
  const text = readFirstString(params.args, ["text", "message", "body", "content"]);
  if (text) {
    params.telemetry.messagingToolSentTexts.push(text);
  }
  params.telemetry.messagingToolSentMediaUrls.push(...collectMediaUrls(params.args));
  params.telemetry.messagingToolSentTargets.push({
    tool: params.toolName,
    provider: readFirstString(params.args, ["provider", "channel"]) ?? params.toolName,
    accountId: readFirstString(params.args, ["accountId", "account_id"]),
    to: readFirstString(params.args, ["to", "target", "recipient"]),
    threadId: readFirstString(params.args, ["threadId", "thread_id", "messageThreadId"]),
  });
}

function convertToolContent(
  content: TextContent | ImageContent,
): CodexDynamicToolCallOutputContentItem[] {
  if (content.type === "text") {
    return [{ type: "inputText", text: content.text }];
  }
  return [
    {
      type: "inputImage",
      imageUrl: `data:${content.mimeType};base64,${content.data}`,
    },
  ];
}

function toJsonValue(value: unknown): JsonValue {
  try {
    const text = JSON.stringify(value);
    if (!text) {
      return {};
    }
    return JSON.parse(text) as JsonValue;
  } catch {
    return {};
  }
}

function jsonObjectToRecord(value: JsonValue | undefined): Record<string, unknown> {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return {};
  }
  return value as Record<string, unknown>;
}

function readFirstString(record: Record<string, unknown>, keys: string[]): string | undefined {
  for (const key of keys) {
    const value = record[key];
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
    if (typeof value === "number" && Number.isFinite(value)) {
      return String(value);
    }
  }
  return undefined;
}

function collectMediaUrls(record: Record<string, unknown>): string[] {
  const urls: string[] = [];
  for (const key of ["mediaUrl", "media_url", "imageUrl", "image_url"]) {
    const value = record[key];
    if (typeof value === "string" && value.trim()) {
      urls.push(value.trim());
    }
  }
  for (const key of ["mediaUrls", "media_urls", "imageUrls", "image_urls"]) {
    const value = record[key];
    if (!Array.isArray(value)) {
      continue;
    }
    for (const entry of value) {
      if (typeof entry === "string" && entry.trim()) {
        urls.push(entry.trim());
      }
    }
  }
  return urls;
}

function isCronAddAction(args: Record<string, unknown>): boolean {
  const action = args.action;
  return typeof action === "string" && action.trim().toLowerCase() === "add";
}
