/**
 * Validates console commands before execution to prevent dangerous operations.
 * Blocks crash-inducing commands, shell injection, and Python execution.
 */
export class CommandValidator {
    /**
     * Commands that can crash the engine or cause severe instability.
     * These are blocked unconditionally.
     */
    private static readonly DANGEROUS_COMMANDS = [
        // Engine termination commands
        'quit', 'exit', 'kill', 'crash',
        // Crash-inducing commands
        'r.gpucrash', 'r.crash', 'debug crash', 'forcecrash', 'debug break',
        'assert false', 'check(false)',
        // View buffer commands that can crash on some hardware
        'viewmode visualizebuffer basecolor',
        'viewmode visualizebuffer worldnormal',
        // Heavy operations that can cause access violations if systems not initialized
        'buildpaths', 'rebuildnavigation',
        // Heavy debug commands that can stall or crash
        'obj garbage', 'obj list', 'memreport',
        // Potentially destructive without proper setup
        'delete', 'destroy'
    ];

    /**
     * Tokens that indicate shell injection or external system access attempts.
     * Any command containing these is blocked.
     */
    private static readonly FORBIDDEN_TOKENS = [
        // Shell commands (Windows/Unix)
        'shutdown', 'reboot', 'rmdir', 'mklink',
        // Python injection attempts
        'import os', 'import subprocess', 'subprocess.', 'os.system',
        'exec(', 'eval(', '__import__', 'import sys', 'import importlib',
        'with open', 'open(', 'write(', 'read('
    ];

    /**
     * Regex patterns for forbidden tokens to handle flexible whitespace.
     */
    private static readonly FORBIDDEN_PATTERNS = [
        // Dangerous shell commands (with word boundaries to prevent substring matching and allow flexible whitespace)
        /\b(?:rm|del|format|copy|move|start)\b/i,
        // Python imports with whitespace
        /import\s+(?:os|sys|subprocess|importlib|shutil)/i,
        /from\s+(?:os|sys|subprocess|importlib|shutil)\s+import/i,
        // Function calls with flexible whitespace before parenthesis
        /(?:exec|eval|open|write|read|system)\s*\(/i,
        // Specific dangerous constructs
        /__import__\s*\(/i,
        /subprocess\./i,
        /os\.system/i,
        /start\s+"/i,
    ];

    /**
     * Patterns that indicate obviously invalid commands.
     * Used to warn about likely typos or invalid input.
     */
    private static readonly INVALID_PATTERNS = [
        /^\d+$/,  // Just numbers
        /^invalid_command/i,
        /^this_is_not_a_valid/i,
    ];

    /**
     * Pre-compiled patterns for dangerous commands using word boundaries.
     * This prevents false positives like 'show exit menu' matching 'exit'.
     */
    private static readonly DANGEROUS_PATTERNS = CommandValidator.DANGEROUS_COMMANDS.map(
        cmd => new RegExp(`(?:^|\\s)${cmd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`, 'i')
    );

    /**
     * Validates a console command for safety before execution.
     * @param command - The console command string to validate
     * @throws Error if the command is dangerous, contains forbidden tokens, or is invalid
     */
    static validate(command: string): void {
        if (!command || typeof command !== 'string') {
            throw new Error('Invalid command: must be a non-empty string');
        }

        const cmdTrimmed = command.trim();
        if (cmdTrimmed.length === 0) {
            return; // Empty commands are technically valid (no-op)
        }

        if (cmdTrimmed.includes('\n') || cmdTrimmed.includes('\r')) {
            throw new Error('Multi-line console commands are not allowed. Send one command per call.');
        }

        const cmdLower = cmdTrimmed.toLowerCase();

        // Check for 'py' or 'python' followed by any whitespace or end of string
        // This catches 'py', 'py ', 'python', 'python ' etc. to prevent bypasses
        if (/^(?:py|python)(?:\s|$)/.test(cmdLower)) {
            throw new Error('Python console commands are blocked from external calls for safety.');
        }

        // Use word-boundary matching to avoid false positives like 'show exit menu'
        if (this.DANGEROUS_PATTERNS.some(pattern => pattern.test(cmdLower))) {
            throw new Error(`Dangerous command blocked: ${command}`);
        }

        if (cmdLower.includes('&&') || cmdLower.includes('||')) {
            throw new Error('Command chaining with && or || is blocked for safety.');
        }

        // Block semicolon and pipe which can also be used for command chaining/injection
        if (cmdTrimmed.includes(';')) {
            throw new Error('Command chaining with ; (semicolon) is blocked for safety.');
        }
        if (cmdTrimmed.includes('|')) {
            throw new Error('Command piping with | is blocked for safety.');
        }

        // Check for forbidden tokens (simple string match)
        if (this.FORBIDDEN_TOKENS.some(token => cmdLower.includes(token))) {
            throw new Error(`Command contains unsafe token and was blocked: ${command}`);
        }

        // Check for forbidden patterns (regex match for flexible whitespace)
        if (this.FORBIDDEN_PATTERNS.some(pattern => pattern.test(cmdLower))) {
             throw new Error(`Command contains unsafe pattern and was blocked: ${command}`);
        }

        // Block backticks which can be used for shell execution
        if (cmdTrimmed.includes('`')) {
            throw new Error('Backtick characters are blocked for safety.');
        }
    }

    /**
     * Check if a command looks like an obviously invalid or mistyped command.
     * @param command - The command to check
     * @returns true if the command matches known invalid patterns
     */
    static isLikelyInvalid(command: string): boolean {
        const cmdTrimmed = command.trim();
        return this.INVALID_PATTERNS.some(pattern => pattern.test(cmdTrimmed));
    }

    /**
     * Get the priority level of a command for throttling purposes.
     * Lower numbers indicate heavier operations that need more throttling.
     * @param command - The command to evaluate
     * @returns Priority level (1=heavy, 5=medium, 7=default, 8-9=light)
     */
    static getPriority(command: string): number {
        const normalized = command.trim().toLowerCase();

        if (normalized.includes('buildlighting') || normalized.includes('buildpaths')) {
            return 1; // Heavy operation
        } else if (normalized.includes('summon') || normalized.includes('spawn')) {
            return 5; // Medium operation
        } else if (normalized.startsWith('stat')) {
            return 8; // Dedicated throttling for stat commands
        } else if (normalized.startsWith('show')) {
            return 9; // Light operation
        }
        return 7; // Default priority
    }
}
