import type { ChatMessage } from "../types.ts";

export type CommandDefinition = {
  id: number;
  name: string;
  aliases?: string[];
  permission: "everyone" | "subscriber" | "moderator" | "owner";
  enabled: boolean;
  global_cooldown_seconds: number;
  user_cooldown_seconds: number;
};

export type CommandInvocation = {
  commandId: number;
  name: string;
  args: string[];
  message: ChatMessage;
};

export class CommandProcessor {
  private readonly globalCooldowns = new Map<string, number>();
  private readonly userCooldowns = new Map<string, number>();
  private lastCooldownPruneAt = 0;

  parse(
    message: ChatMessage,
    commands: CommandDefinition[],
    prefix = "!"
  ): { invocation?: CommandInvocation; reason?: string } {
    if (!message.content.startsWith(prefix)) return { reason: "not_command" };
    const [rawName = "", ...args] = message.content.slice(prefix.length).trim().split(/\s+/);
    const name = rawName.toLowerCase();
    if (!name) return { reason: "empty" };
    const command = commands.find((item) => item.enabled && (item.name === name || item.aliases?.includes(name)));
    if (!command) return { reason: "unknown" };
    if (!this.allowed(command.permission, message.sender.roles)) return { reason: "permission" };
    const now = Date.now();
    this.pruneCooldowns(now);
    const globalKey = `${message.channel_id}:${command.id}`;
    const userKey = `${globalKey}:${message.sender.kick_user_id}`;
    if ((this.globalCooldowns.get(globalKey) ?? 0) > now) return { reason: "global_cooldown" };
    if ((this.userCooldowns.get(userKey) ?? 0) > now) return { reason: "user_cooldown" };
    this.globalCooldowns.set(globalKey, now + Math.max(0, command.global_cooldown_seconds) * 1_000);
    this.userCooldowns.set(userKey, now + Math.max(0, command.user_cooldown_seconds) * 1_000);
    return { invocation: { commandId: command.id, name: command.name, args, message } };
  }

  private pruneCooldowns(now: number): void {
    // A channel can receive commands from a very large audience.  Cooldown
    // keys are short-lived, so retaining each historic viewer indefinitely
    // makes the always-on engine consume memory for no benefit.
    if (now - this.lastCooldownPruneAt < 300_000) return;
    this.lastCooldownPruneAt = now;
    for (const [key, expiresAt] of this.globalCooldowns) if (expiresAt <= now) this.globalCooldowns.delete(key);
    for (const [key, expiresAt] of this.userCooldowns) if (expiresAt <= now) this.userCooldowns.delete(key);
  }

  private allowed(permission: CommandDefinition["permission"], roles: string[]): boolean {
    if (permission === "everyone") return true;
    const normalized = new Set(roles.map((role) => role.toLowerCase()));
    if (permission === "subscriber") return normalized.has("subscriber") || normalized.has("sub") || normalized.has("moderator") || normalized.has("owner");
    if (permission === "moderator") return normalized.has("moderator") || normalized.has("mod") || normalized.has("owner");
    return normalized.has("owner") || normalized.has("broadcaster");
  }
}
