/**
 * @fileoverview Code sanitizer for GameInterpreter.
 *
 * Validates user-submitted code before handing it to js-interpreter,
 * defending against common DoS patterns (infinite loops, excessive input, etc.)
 *
 * @module interpreters.CodeSanitizer
 */

/**
 * Error thrown when code fails sanitization.
 * The `rule` property identifies which rule triggered the rejection.
 */
export class CodeSanitizationError extends Error {
  /**
   * @param {string} message - Human-readable reason.
   * @param {string} rule    - Rule identifier (e.g. "max_length").
   */
  constructor(message, rule) {
    super(message);
    this.name = "CodeSanitizationError";
    this.rule = rule;
  }
}

/**
 * Default sanitization rules applied to every code submission.
 *
 * @type {Array<{id: string, check: (code: string) => boolean, message: string}>}
 */
export const DEFAULT_RULES = [
  {
    id: "max_length",
    check: (code) => code.length > 50_000,
    message:
      "O código é demasiado longo para ser executado (máx. 50 000 caracteres).",
  },
  {
    id: "infinite_while",
    check: (code) =>
      /while\s*\(\s*(true|!0|1)\s*\)/i.test(code) &&
      !/break\b/.test(code) &&
      !/return\b/.test(code),
    message:
      "Encontrado loop infinito (while(true) sem break/return). Adiciona uma condição de saída.",
  },
  {
    id: "infinite_for",
    check: (code) =>
      /for\s*\(\s*;?\s*;?\s*;?\s*\)/.test(code) &&
      !/break\b/.test(code) &&
      !/return\b/.test(code),
    message:
      "Encontrado loop infinito (for(;;) sem break/return). Adiciona uma condição de saída.",
  },
  {
    id: "excessive_nesting",
    check: (code) => {
      let depth = 0;
      let maxDepth = 0;
      for (const ch of code) {
        if (ch === "{") {
          maxDepth = Math.max(maxDepth, ++depth);
        } else if (ch === "}") {
          depth = Math.max(0, depth - 1);
        }
      }
      return maxDepth > 15;
    },
    message:
      "O código tem demasiados níveis de aninhamento (máx. 15 blocos {}).",
  },
];

/**
 * Validates code against the default rules plus any extra rules provided.
 * Throws {@link CodeSanitizationError} on the first rule that fails.
 *
 * @param {string} code        - Source code to validate.
 * @param {Array}  [extraRules=[]] - Additional rules to apply after the defaults.
 * @throws {CodeSanitizationError}
 */
export function sanitizeCode(code, extraRules = []) {
  if (typeof code !== "string") {
    throw new CodeSanitizationError(
      "O código deve ser uma string.",
      "type_check",
    );
  }

  const rules = [...DEFAULT_RULES, ...extraRules];
  for (const rule of rules) {
    if (rule.check(code)) {
      throw new CodeSanitizationError(rule.message, rule.id);
    }
  }
}
