// Discord plugin module implements subagent hooks behavior.
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-plugin-common";
import {
  formatThreadBindingDisabledError,
  formatThreadBindingSpawnDisabledError,
  resolveThreadBindingSpawnPolicy,
} from "openclaw/plugin-sdk/conversation-runtime";
import {
  normalizeOptionalLowercaseString,
  normalizeOptionalStringifiedId,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { resolveDiscordAccount } from "./accounts.js";
import {
  autoBindSpawnedDiscordSubagent,
  listThreadBindingsBySessionKey,
  type ThreadBindingTargetKind,
  unbindThreadBindingsBySessionKey,
} from "./monitor/thread-bindings.js";

function summarizeError(err: unknown): string {
  if (err instanceof Error) {
    return err.message;
  }
  if (typeof err === "string") {
    return err;
  }
  return "error";
}

type DiscordSubagentSpawningEvent = {
  threadRequested?: boolean;
  requester?: {
    channel?: string;
    accountId?: string;
    to?: string;
    threadId?: string | number;
  };
  childSessionKey: string;
  agentId: string;
  label?: string;
};

type DiscordSubagentEndedEvent = {
  targetSessionKey: string;
  accountId?: string;
  targetKind?: ThreadBindingTargetKind;
  reason?: string;
  sendFarewell?: boolean;
};

type DiscordSubagentDeliveryTargetEvent = {
  expectsCompletionMessage?: boolean;
  childSessionKey: string;
  requesterOrigin?: {
    channel?: string;
    accountId?: string;
    threadId?: string | number;
  };
};

type DiscordSubagentSpawningResult =
  | {
      status: "ok";
      threadBindingReady?: boolean;
      deliveryOrigin?: {
        channel: "discord";
        accountId?: string;
        to: string;
        threadId?: string | number;
      };
    }
  | { status: "error"; error: string }
  | undefined;

type DiscordSubagentDeliveryTargetResult =
  | {
      origin: {
        channel: "discord";
        accountId?: string;
        to: string;
        threadId?: string | number;
      };
    }
  | undefined;

function normalizeThreadBindingTargetKind(raw?: string): ThreadBindingTargetKind | undefined {
  const normalized = normalizeOptionalLowercaseString(raw);
  if (normalized === "subagent" || normalized === "acp") {
    return normalized;
  }
  return undefined;
}

export async function handleDiscordSubagentSpawning(
  api: OpenClawPluginApi,
  event: DiscordSubagentSpawningEvent,
): Promise<DiscordSubagentSpawningResult> {
  if (!event.threadRequested) {
    return undefined;
  }
  const channel = normalizeOptionalLowercaseString(event.requester?.channel);
  if (channel !== "discord") {
    return undefined;
  }
  const account = resolveDiscordAccount({
    cfg: api.config,
    accountId: event.requester?.accountId,
  });
  const threadBindingPolicy = resolveThreadBindingSpawnPolicy({
    cfg: api.config,
    channel: "discord",
    accountId: account.accountId,
    kind: "subagent",
  });
  if (!threadBindingPolicy.enabled) {
    return {
      status: "error" as const,
      error: formatThreadBindingDisabledError({
        channel: threadBindingPolicy.channel,
        accountId: threadBindingPolicy.accountId,
        kind: "subagent",
      }),
    };
  }
  if (!threadBindingPolicy.spawnEnabled) {
    return {
      status: "error" as const,
      error: formatThreadBindingSpawnDisabledError({
        channel: threadBindingPolicy.channel,
        accountId: threadBindingPolicy.accountId,
        kind: "subagent",
      }),
    };
  }
  try {
    const agentId = event.agentId?.trim() || "subagent";
    const binding = await autoBindSpawnedDiscordSubagent({
      cfg: api.config,
      accountId: account.accountId,
      channel: event.requester?.channel,
      to: event.requester?.to,
      threadId: event.requester?.threadId,
      childSessionKey: event.childSessionKey,
      agentId,
      label: event.label,
      boundBy: "system",
    });
    if (!binding) {
      return {
        status: "error" as const,
        error:
          "Unable to create or bind a Discord thread for this subagent session. Session mode is unavailable for this target.",
      };
    }
    return {
      status: "ok" as const,
      threadBindingReady: true,
      deliveryOrigin: {
        channel: "discord",
        accountId: account.accountId,
        to: `channel:${binding.threadId}`,
        threadId: binding.threadId,
      },
    };
  } catch (err) {
    return {
      status: "error" as const,
      error: `Discord thread bind failed: ${summarizeError(err)}`,
    };
  }
}

export function handleDiscordSubagentEnded(event: DiscordSubagentEndedEvent) {
  unbindThreadBindingsBySessionKey({
    targetSessionKey: event.targetSessionKey,
    accountId: event.accountId,
    targetKind: normalizeThreadBindingTargetKind(event.targetKind),
    reason: event.reason,
    sendFarewell: event.sendFarewell,
  });
}

export function handleDiscordSubagentDeliveryTarget(
  event: DiscordSubagentDeliveryTargetEvent,
): DiscordSubagentDeliveryTargetResult {
  if (!event.expectsCompletionMessage) {
    return undefined;
  }
  const requesterChannel = normalizeOptionalLowercaseString(event.requesterOrigin?.channel);
  if (requesterChannel !== "discord") {
    return undefined;
  }
  const requesterAccountId = event.requesterOrigin?.accountId?.trim();
  const requesterThreadId =
    event.requesterOrigin?.threadId != null && event.requesterOrigin.threadId !== ""
      ? (normalizeOptionalStringifiedId(event.requesterOrigin.threadId) ?? "")
      : "";
  const bindings = listThreadBindingsBySessionKey({
    targetSessionKey: event.childSessionKey,
    ...(requesterAccountId ? { accountId: requesterAccountId } : {}),
    targetKind: "subagent",
  });
  if (bindings.length === 0) {
    return undefined;
  }

  let binding: (typeof bindings)[number] | undefined;
  if (requesterThreadId) {
    binding = bindings.find((entry) => {
      if (entry.threadId !== requesterThreadId) {
        return false;
      }
      if (requesterAccountId && entry.accountId !== requesterAccountId) {
        return false;
      }
      return true;
    });
  }
  if (!binding && bindings.length === 1) {
    binding = bindings[0];
  }
  if (!binding) {
    return undefined;
  }
  return {
    origin: {
      channel: "discord" as const,
      accountId: binding.accountId,
      to: `channel:${binding.threadId}`,
      threadId: binding.threadId,
    },
  };
}
