// Implements `openclaw channels resolve` for provider-specific user/group target resolution.
import {
  normalizeLowercaseStringOrEmpty,
  normalizeOptionalLowercaseString,
} from "@openclaw/normalization-core/string-coerce";
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
import { getChannelPlugin } from "../../channels/plugins/index.js";
import type {
  ChannelResolveKind,
  ChannelResolveResult,
} from "../../channels/plugins/types.adapters.js";
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
import { formatCliCommand } from "../../cli/command-format.js";
import { getChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
import { formatUnsupportedChannelActionMessage } from "../../cli/error-format.js";
import { getRuntimeConfig, readConfigFileSnapshot } from "../../config/config.js";
import { danger } from "../../globals.js";
import { resolveMessageChannelSelection } from "../../infra/outbound/channel-selection.js";
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
import { resolveInstallableChannelPlugin } from "../channel-setup/channel-plugin-resolution.js";
import { persistResolvedChannelPluginConfig } from "./plugin-config-persistence.js";

export type ChannelsResolveOptions = {
  channel?: string;
  account?: string;
  kind?: "auto" | "user" | "group" | "channel";
  json?: boolean;
  entries?: string[];
};

type ResolveResult = {
  input: string;
  resolved: boolean;
  id?: string;
  name?: string;
  error?: string;
  note?: string;
};

function resolvePreferredKind(
  kind?: ChannelsResolveOptions["kind"],
): ChannelResolveKind | undefined {
  if (!kind || kind === "auto") {
    return undefined;
  }
  if (kind === "user") {
    return "user";
  }
  return "group";
}

function detectAutoKind(input: string): ChannelResolveKind {
  const trimmed = input.trim();
  if (!trimmed) {
    return "group";
  }
  if (trimmed.startsWith("@")) {
    return "user";
  }
  if (/^<@!?/.test(trimmed)) {
    return "user";
  }
  if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
    return "user";
  }
  if (/^user:/i.test(trimmed)) {
    return "user";
  }
  return "group";
}

function detectAutoKindForPlugin(
  input: string,
  plugin?: {
    id: string;
    meta?: {
      aliases?: readonly string[];
    };
  },
): ChannelResolveKind {
  const generic = detectAutoKind(input);
  if (generic === "user" || !plugin) {
    return generic;
  }
  const trimmed = input.trim();
  const lowered = normalizeLowercaseStringOrEmpty(trimmed);
  const prefixes = [plugin.id, ...(plugin.meta?.aliases ?? [])]
    .map((entry) => normalizeOptionalLowercaseString(entry))
    .filter((entry): entry is string => Boolean(entry));
  for (const prefix of prefixes) {
    if (!lowered.startsWith(`${prefix}:`)) {
      continue;
    }
    const remainder = lowered.slice(prefix.length + 1);
    if (
      remainder.startsWith("group:") ||
      remainder.startsWith("channel:") ||
      remainder.startsWith("room:") ||
      remainder.startsWith("conversation:") ||
      remainder.startsWith("spaces/") ||
      remainder.startsWith("channels/")
    ) {
      return "group";
    }
    return "user";
  }
  return generic;
}

function formatResolveResult(result: ResolveResult): string {
  if (!result.resolved || !result.id) {
    return `${result.input} -> unresolved`;
  }
  const name = result.name ? ` (${result.name})` : "";
  const note = result.note ? ` [${result.note}]` : "";
  return `${result.input} -> ${result.id}${name}${note}`;
}

/** Resolve user/group/channel labels into plugin-specific stable target ids. */
export async function channelsResolveCommand(opts: ChannelsResolveOptions, runtime: RuntimeEnv) {
  const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
  const loadedRaw = getRuntimeConfig();
  let { effectiveConfig: cfg } = await resolveCommandConfigWithSecrets({
    config: loadedRaw,
    commandName: "channels resolve",
    targetIds: getChannelsCommandSecretTargetIds(),
    mode: "read_only_operational",
    runtime,
    autoEnable: true,
  });
  const entries = normalizeStringEntries(opts.entries);
  if (entries.length === 0) {
    throw new Error(
      `At least one entry is required. Example: ${formatCliCommand("openclaw channels resolve --channel discord <name-or-id>")}.`,
    );
  }

  const explicitChannel = opts.channel?.trim();
  const resolvedExplicit = explicitChannel
    ? await resolveInstallableChannelPlugin({
        cfg,
        runtime,
        rawChannel: explicitChannel,
        allowInstall: false,
        supports: (plugin) => Boolean(plugin.resolver?.resolveTargets),
      })
    : null;
  if (explicitChannel && resolvedExplicit?.catalogEntry && !resolvedExplicit.plugin) {
    throw new Error(
      `Channel plugin "${resolvedExplicit.catalogEntry.id}" is not installed. Run ${formatCliCommand(`openclaw channels add --channel ${resolvedExplicit.catalogEntry.id}`)} first.`,
    );
  }
  if (resolvedExplicit?.configChanged) {
    cfg = await persistResolvedChannelPluginConfig({
      resolved: resolvedExplicit,
      baseHash: (await sourceSnapshotPromise)?.hash,
      runtime,
    });
  }

  const selection = explicitChannel
    ? {
        channel: resolvedExplicit?.channelId,
      }
    : await resolveMessageChannelSelection({
        cfg,
        channel: opts.channel ?? null,
      });
  const plugin =
    (explicitChannel ? resolvedExplicit?.plugin : undefined) ??
    (selection.channel ? getChannelPlugin(selection.channel) : undefined);
  if (!plugin?.resolver?.resolveTargets) {
    const channelText = selection.channel ?? explicitChannel ?? "";
    throw new Error(
      formatUnsupportedChannelActionMessage({
        channel: channelText,
        action: "resolve",
      }),
    );
  }
  const preferredKind = resolvePreferredKind(opts.kind);

  let results: ResolveResult[];
  if (preferredKind) {
    const resolved = await plugin.resolver.resolveTargets({
      cfg,
      accountId: opts.account ?? null,
      inputs: entries,
      kind: preferredKind,
      runtime,
    });
    results = resolved.map((entry) => ({
      input: entry.input,
      resolved: entry.resolved,
      id: entry.id,
      name: entry.name,
      note: entry.note,
    }));
  } else {
    const byKind = new Map<ChannelResolveKind, string[]>();
    for (const entry of entries) {
      const kind = detectAutoKindForPlugin(entry, plugin);
      byKind.set(kind, [...(byKind.get(kind) ?? []), entry]);
    }
    const resolved: ChannelResolveResult[] = [];
    for (const [kind, inputs] of byKind.entries()) {
      const batch = await plugin.resolver.resolveTargets({
        cfg,
        accountId: opts.account ?? null,
        inputs,
        kind,
        runtime,
      });
      resolved.push(...batch);
    }
    const byInput = new Map(resolved.map((entry) => [entry.input, entry]));
    results = entries.map((input) => {
      const entry = byInput.get(input);
      return {
        input,
        resolved: entry?.resolved ?? false,
        id: entry?.id,
        name: entry?.name,
        note: entry?.note,
      };
    });
  }

  if (opts.json) {
    writeRuntimeJson(runtime, results);
    return;
  }

  for (const result of results) {
    if (result.resolved && result.id) {
      runtime.log(formatResolveResult(result));
    } else {
      runtime.error(
        danger(
          `${result.input} -> unresolved${result.error ? ` (${result.error})` : result.note ? ` (${result.note})` : ""}`,
        ),
      );
    }
  }
}
