import type { TwitchAccountConfig, TwitchChatMessage } from "./types.js";

/**
 * Result of checking access control for a Twitch message
 */
export type TwitchAccessControlResult = {
  allowed: boolean;
  reason?: string;
  matchKey?: string;
  matchSource?: string;
};

/**
 * Check if a Twitch message should be allowed based on account configuration
 *
 * This function implements the access control logic for incoming Twitch messages,
 * checking allowlists, role-based restrictions, and mention requirements.
 *
 * Priority order:
 * 1. If `requireMention` is true, message must mention the bot
 * 2. If `allowFrom` is set, sender must be in the allowlist (by user ID)
 * 3. If `allowedRoles` is set (and `allowFrom` is not), sender must have at least one role
 *
 * Note: `allowFrom` is a hard allowlist. When set, only those user IDs are allowed.
 * Use `allowedRoles` as an alternative when you don't want to maintain an allowlist.
 *
 * Available roles:
 * - "moderator": Moderators
 * - "owner": Channel owner/broadcaster
 * - "vip": VIPs
 * - "subscriber": Subscribers
 * - "all": Anyone in the chat
 */
export function checkTwitchAccessControl(params: {
  message: TwitchChatMessage;
  account: TwitchAccountConfig;
  botUsername: string;
}): TwitchAccessControlResult {
  const { message, account, botUsername } = params;

  if (account.requireMention ?? true) {
    const mentions = extractMentions(message.message);
    if (!mentions.includes(botUsername.toLowerCase())) {
      return {
        allowed: false,
        reason: "message does not mention the bot (requireMention is enabled)",
      };
    }
  }

  if (account.allowFrom && account.allowFrom.length > 0) {
    const allowFrom = account.allowFrom;
    const senderId = message.userId;

    if (!senderId) {
      return {
        allowed: false,
        reason: "sender user ID not available for allowlist check",
      };
    }

    if (allowFrom.includes(senderId)) {
      return {
        allowed: true,
        matchKey: senderId,
        matchSource: "allowlist",
      };
    }

    return {
      allowed: false,
      reason: "sender is not in allowFrom allowlist",
    };
  }

  if (account.allowedRoles && account.allowedRoles.length > 0) {
    const allowedRoles = account.allowedRoles;

    // "all" grants access to everyone
    if (allowedRoles.includes("all")) {
      return {
        allowed: true,
        matchKey: "all",
        matchSource: "role",
      };
    }

    const hasAllowedRole = checkSenderRoles({
      message,
      allowedRoles,
    });

    if (!hasAllowedRole) {
      return {
        allowed: false,
        reason: `sender does not have any of the required roles: ${allowedRoles.join(", ")}`,
      };
    }

    return {
      allowed: true,
      matchKey: allowedRoles.join(","),
      matchSource: "role",
    };
  }

  return {
    allowed: true,
  };
}

/**
 * Check if the sender has any of the allowed roles
 */
function checkSenderRoles(params: { message: TwitchChatMessage; allowedRoles: string[] }): boolean {
  const { message, allowedRoles } = params;
  const { isMod, isOwner, isVip, isSub } = message;

  for (const role of allowedRoles) {
    switch (role) {
      case "moderator":
        if (isMod) {
          return true;
        }
        break;
      case "owner":
        if (isOwner) {
          return true;
        }
        break;
      case "vip":
        if (isVip) {
          return true;
        }
        break;
      case "subscriber":
        if (isSub) {
          return true;
        }
        break;
    }
  }

  return false;
}

/**
 * Extract @mentions from a Twitch chat message
 *
 * Returns a list of lowercase usernames that were mentioned in the message.
 * Twitch mentions are in the format @username.
 */
export function extractMentions(message: string): string[] {
  const mentionRegex = /@(\w+)/g;
  const mentions: string[] = [];
  let match: RegExpExecArray | null;

  while ((match = mentionRegex.exec(message)) !== null) {
    const username = match[1];
    if (username) {
      mentions.push(username.toLowerCase());
    }
  }

  return mentions;
}
