import {
  applyChannelMatchMeta,
  buildChannelKeyCandidates,
  resolveChannelEntryMatchWithFallback,
  type ChannelMatchSource,
} from "openclaw/plugin-sdk/channel-targets";
import type { SlackReactionNotificationMode } from "openclaw/plugin-sdk/config-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { SlackMessageEvent } from "../types.js";
import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";

export type SlackChannelConfigResolved = {
  allowed: boolean;
  requireMention: boolean;
  allowBots?: boolean;
  users?: Array<string | number>;
  skills?: string[];
  systemPrompt?: string;
  matchKey?: string;
  matchSource?: ChannelMatchSource;
};

export type SlackChannelConfigEntry = {
  enabled?: boolean;
  requireMention?: boolean;
  allowBots?: boolean;
  users?: Array<string | number>;
  skills?: string[];
  systemPrompt?: string;
};

export type SlackChannelConfigEntries = Record<string, SlackChannelConfigEntry>;

function firstDefined<T>(...values: Array<T | undefined>) {
  for (const value of values) {
    if (typeof value !== "undefined") {
      return value;
    }
  }
  return undefined;
}

export function shouldEmitSlackReactionNotification(params: {
  mode: SlackReactionNotificationMode | undefined;
  botId?: string | null;
  messageAuthorId?: string | null;
  userId: string;
  userName?: string | null;
  allowlist?: Array<string | number> | null;
  allowNameMatching?: boolean;
}) {
  const { mode, botId, messageAuthorId, userId, userName, allowlist } = params;
  const effectiveMode = mode ?? "own";
  if (effectiveMode === "off") {
    return false;
  }
  if (effectiveMode === "own") {
    if (!botId || !messageAuthorId) {
      return false;
    }
    return messageAuthorId === botId;
  }
  if (effectiveMode === "allowlist") {
    if (!Array.isArray(allowlist) || allowlist.length === 0) {
      return false;
    }
    const users = normalizeAllowListLower(allowlist);
    return allowListMatches({
      allowList: users,
      id: userId,
      name: userName ?? undefined,
      allowNameMatching: params.allowNameMatching,
    });
  }
  return true;
}

export function resolveSlackChannelLabel(params: { channelId?: string; channelName?: string }) {
  const channelName = params.channelName?.trim();
  if (channelName) {
    const slug = normalizeSlackSlug(channelName);
    return `#${slug || channelName}`;
  }
  const channelId = params.channelId?.trim();
  return channelId ? `#${channelId}` : "unknown channel";
}

export function resolveSlackChannelConfig(params: {
  channelId: string;
  channelName?: string;
  channels?: SlackChannelConfigEntries;
  channelKeys?: string[];
  defaultRequireMention?: boolean;
  allowNameMatching?: boolean;
}): SlackChannelConfigResolved | null {
  const {
    channelId,
    channelName,
    channels,
    channelKeys,
    defaultRequireMention,
    allowNameMatching,
  } = params;
  const entries = channels ?? {};
  const keys = channelKeys ?? Object.keys(entries);
  const normalizedName = channelName ? normalizeSlackSlug(channelName) : "";
  const directName = channelName ? channelName.trim() : "";
  // Slack always delivers channel IDs in uppercase (e.g. C0ABC12345) but
  // operators commonly write them in lowercase in their config. Add both
  // case variants so the lookup is case-insensitive without requiring a full
  // entry-scan. buildChannelKeyCandidates deduplicates identical keys.
  const channelIdLower = normalizeLowercaseStringOrEmpty(channelId);
  const channelIdUpper = channelId.toUpperCase();
  const candidates = buildChannelKeyCandidates(
    channelId,
    channelIdLower !== channelId ? channelIdLower : undefined,
    channelIdUpper !== channelId ? channelIdUpper : undefined,
    allowNameMatching ? (channelName ? `#${directName}` : undefined) : undefined,
    allowNameMatching ? directName : undefined,
    allowNameMatching ? normalizedName : undefined,
  );
  const match = resolveChannelEntryMatchWithFallback({
    entries,
    keys: candidates,
    wildcardKey: "*",
  });
  const { entry: matched, wildcardEntry: fallback } = match;

  const requireMentionDefault = defaultRequireMention ?? true;
  if (keys.length === 0) {
    return { allowed: true, requireMention: requireMentionDefault };
  }
  if (!matched && !fallback) {
    return { allowed: false, requireMention: requireMentionDefault };
  }

  const resolved = matched ?? fallback ?? {};
  const allowed = firstDefined(resolved.enabled, fallback?.enabled, true) ?? true;
  const requireMention =
    firstDefined(resolved.requireMention, fallback?.requireMention, requireMentionDefault) ??
    requireMentionDefault;
  const allowBots = firstDefined(resolved.allowBots, fallback?.allowBots);
  const users = firstDefined(resolved.users, fallback?.users);
  const skills = firstDefined(resolved.skills, fallback?.skills);
  const systemPrompt = firstDefined(resolved.systemPrompt, fallback?.systemPrompt);
  const result: SlackChannelConfigResolved = {
    allowed,
    requireMention,
    allowBots,
    users,
    skills,
    systemPrompt,
  };
  return applyChannelMatchMeta(result, match);
}

export type { SlackMessageEvent };
