#!/usr/bin/env php
<?php
/**
 * Comprehensive PHP/WordPress Plugin Static Analyzer
 * Detects runtime errors, duplicate declarations, missing dependencies, and code issues
 * Optimized for minimal memory usage via streaming token parsing
 */

if ($argv[1] ?? null === '--help' || $argv[1] ?? null === '-h') {
    echo "Usage: {$argv[0]} /path/to/plugin [options]\n";
    echo "\nOptions:\n";
    echo "  --verbose      Show detailed output\n";
    echo "  --strict       Enable stricter checks (may have false positives)\n";
    echo "  --no-cache     Skip caching (useful for CI/CD)\n";
    echo "  --quiet        Suppress informational messages\n";
    exit(0);
}

if ($argc < 2) {
    fwrite(STDERR, "Usage: {$argv[0]} /path/to/plugin\n");
    exit(1);
}

$pluginDir = rtrim($argv[1], '/');
$verbose = in_array('--verbose', $argv);
$strict = in_array('--strict', $argv);
$quiet = in_array('--quiet', $argv);

if (!is_dir($pluginDir)) {
    fwrite(STDERR, "Error: Directory not found: {$pluginDir}\n");
    exit(1);
}

$stats = [
    'files' => 0,
    'classes' => 0,
    'interfaces' => 0,
    'traits' => 0,
    'functions' => 0,
    'constants' => 0,
    'includes' => 0
];

$issues = [
    'duplicates' => [],
    'missing_includes' => [],
    'missing_classes' => [],
    'type_hints' => [],
    'circular_deps' => [],
    'this_usage' => [],
    'static_calls' => [],
    'file_paths' => [],
    'activation' => [],
    'undefined_functions' => [],
    'early_function_calls' => [],
    'undefined_arrays' => [],
    'undefined_array_keys' => [],
    'css_overlaps' => []
];

$declarations = [
    'classes' => [],
    'interfaces' => [],
    'traits' => [],
    'functions' => [],
    'constants' => [],
    'includes' => []
];

$definedInFile = [];
$functionImplementations = [];
$methodImplementations = [];
$typeHints = [];
$thisUsage = [];
$staticCalls = [];

// Track functions defined and their line numbers
$functionDefinitions = [];
$functionCalls = [];
$variableAssignments = [];
$arrayAccesses = [];
$cssFiles = [];
$variableDeclarations = [];
$arrayKeyAccesses = [];
$arrayKeyDefinitions = [];
$unsafeArrayAccesses = [];

$excludeDirs = ['/vendor/', '/node_modules/', '/.git/', '/cache/', '/logs/', '/tmp/'];

function shouldExclude(string $path, array $excludeDirs): bool {
    foreach ($excludeDirs as $dir) {
        if (strpos($path, $dir) !== false) {
            return true;
        }
    }
    return false;
}

function normalizePath(string $path, string $baseDir): string {
    $path = str_replace(['/', '\\'], '/', $path);
    $baseDir = rtrim(str_replace(['/', '\\'], '/', $baseDir), '/');
    
    if (preg_match('/^\.\.?[\/\\\\]/', $path) || !preg_match('/^[a-zA-Z]:/', $path)) {
        $fullPath = $baseDir . '/' . $path;
        $fullPath = preg_replace('/\/+/', '/', $fullPath);
        return $fullPath;
    }
    
    return $path;
}

function findAbsoluteFile(string $includePath, string $currentFile, string $baseDir): ?string {
    $currentDir = dirname($currentFile);
    $possiblePaths = [
        $currentDir . '/' . $includePath,
        $currentDir . '/' . $includePath . '.php',
        $baseDir . '/' . $includePath,
        $baseDir . '/' . $includePath . '.php',
        $baseDir . '/includes/' . $includePath,
        $baseDir . '/includes/' . $includePath . '.php',
    ];
    
    foreach ($possiblePaths as $path) {
        if (file_exists($path)) {
            return realpath($path) ?: $path;
        }
    }
    
    return null;
}

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($pluginDir, RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::LEAVES_ONLY
);

foreach ($iterator as $file) {
    if ($file->getExtension() !== 'php') {
        continue;
    }
    
    $filePath = $file->getPathname();
    if (shouldExclude($filePath, $excludeDirs)) {
        continue;
    }
    
    $stats['files']++;
    $relativePath = str_replace($pluginDir . '/', '', $filePath);
    $definedInFile[$relativePath] = [];
    
    $content = file_get_contents($filePath, false, null, 0, 2097152);
    if ($content === false) {
        continue;
    }
    
    $tokens = token_get_all($content);
    $lineNumber = 1;
    $namespaces = [''];
    $currentNS = '';
    
    for ($i = 0; $i < count($tokens); $i++) {
        $token = $tokens[$i];
        
        if (is_array($token)) {
            if ($token[2] !== $lineNumber) {
                $lineNumber = $token[2];
            }
            
            switch ($token[0]) {
                case T_NAMESPACE:
                    $nsTokens = [];
                    $j = $i + 1;
                    while (isset($tokens[$j]) && ($tokens[$j] !== ';' && $tokens[$j] !== '{' && $tokens[$j] !== '=')) {
                        if (is_array($tokens[$j]) && in_array($tokens[$j][0], [T_STRING, T_NS_SEPARATOR])) {
                            $nsTokens[] = $tokens[$j][1];
                        }
                        $j++;
                    }
                    $currentNS = implode('', $nsTokens);
                    $namespaces[count($namespaces) - 1] = $currentNS;
                    break;
                    
                case T_CLASS:
                    $className = extractDeclaration($tokens, $i, $namespaces, 'class');
                    if ($className) {
                        $stats['classes']++;
                        $fullName = $currentNS ? $currentNS . '\\' . $className : $className;
                        $declarations['classes'][$fullName][] = ['file' => $relativePath, 'line' => $lineNumber];
                        $definedInFile[$relativePath][] = ['type' => 'class', 'name' => $fullName];
                    }
                    break;
                    
                case T_INTERFACE:
                    $interfaceName = extractDeclaration($tokens, $i, $namespaces, 'interface');
                    if ($interfaceName) {
                        $stats['interfaces']++;
                        $fullName = $currentNS ? $currentNS . '\\' . $interfaceName : $interfaceName;
                        $declarations['interfaces'][$fullName][] = ['file' => $relativePath, 'line' => $lineNumber];
                        $definedInFile[$relativePath][] = ['type' => 'interface', 'name' => $fullName];
                    }
                    break;
                    
                case T_TRAIT:
                    $traitName = extractDeclaration($tokens, $i, $namespaces, 'trait');
                    if ($traitName) {
                        $stats['traits']++;
                        $fullName = $currentNS ? $currentNS . '\\' . $traitName : $traitName;
                        $declarations['traits'][$fullName][] = ['file' => $relativePath, 'line' => $lineNumber];
                        $definedInFile[$relativePath][] = ['type' => 'trait', 'name' => $fullName];
                    }
                    break;
                    
                case T_FUNCTION:
                    $funcName = extractDeclaration($tokens, $i, $namespaces, 'function');
                    if ($funcName) {
                        $stats['functions']++;
                        $fullName = $currentNS ? $currentNS . '\\' . $funcName : $funcName;
                        if (!isset($functionImplementations[$fullName])) {
                            $functionImplementations[$fullName] = [];
                        }
                        $functionImplementations[$fullName][] = ['file' => $relativePath, 'line' => $lineNumber];
                        $definedInFile[$relativePath][] = ['type' => 'function', 'name' => $fullName];

                        // Track function definition for early call detection
                        if (!isset($functionDefinitions[$relativePath])) {
                            $functionDefinitions[$relativePath] = [];
                        }
                        $functionDefinitions[$relativePath][$funcName] = $lineNumber;
                    }
                    break;
                    
                case T_CONST:
                    $constName = extractConstantDeclaration($tokens, $i);
                    if ($constName) {
                        $stats['constants']++;
                        $fullName = $currentNS ? $currentNS . '\\' . $constName : $constName;
                        if (!isset($declarations['constants'][$fullName])) {
                            $declarations['constants'][$fullName] = [];
                        }
                        $declarations['constants'][$fullName][] = ['file' => $relativePath, 'line' => $lineNumber];
                        $definedInFile[$relativePath][] = ['type' => 'constant', 'name' => $fullName];
                    }
                    break;
                    
                case T_NEW:
                    $className = extractAfterToken($tokens, $i);
                    if ($className && !isInternalClass($className)) {
                        $typeHints[$className][] = ['file' => $relativePath, 'line' => $lineNumber, 'usage' => 'new'];
                    }
                    break;
                    
                case T_INSTANCEOF:
                    $className = extractAfterToken($tokens, $i);
                    if ($className && !isInternalClass($className)) {
                        $typeHints[$className][] = ['file' => $relativePath, 'line' => $lineNumber, 'usage' => 'instanceof'];
                    }
                    break;
                    
                case T_CATCH:
                    $className = extractCatchClass($tokens, $i);
                    if ($className && !isInternalClass($className)) {
                        $typeHints[$className][] = ['file' => $relativePath, 'line' => $lineNumber, 'usage' => 'catch'];
                    }
                    break;
                    
                case T_EXTENDS:
                    $className = extractAfterToken($tokens, $i);
                    if ($className && !isInternalClass($className)) {
                        $typeHints[$className][] = ['file' => $relativePath, 'line' => $lineNumber, 'usage' => 'extends'];
                    }
                    break;
                    
                case T_IMPLEMENTS:
                    $className = extractAfterToken($tokens, $i);
                    if ($className && !isInternalClass($className)) {
                        $typeHints[$className][] = ['file' => $relativePath, 'line' => $lineNumber, 'usage' => 'implements'];
                    }
                    break;
                    
                case T_INCLUDE:
                case T_INCLUDE_ONCE:
                case T_REQUIRE:
                case T_REQUIRE_ONCE:
                    $includePath = extractIncludePath($tokens, $i);
                    if ($includePath) {
                        $stats['includes']++;
                        $fullPath = findAbsoluteFile($includePath, $filePath, $pluginDir);
                        $declarations['includes'][] = [
                            'path' => $includePath,
                            'file' => $relativePath,
                            'line' => $lineNumber,
                            'resolved' => $fullPath
                        ];

                        if (!$fullPath || !file_exists($fullPath)) {
                            $issues['missing_includes'][] = [
                                'path' => $includePath,
                                'file' => $relativePath,
                                'line' => $lineNumber
                            ];
                        }
                    }
                    break;

                case T_STRING:
                    // Track function calls
                    if (isset($tokens[$i + 1]) && $tokens[$i + 1] === '(') {
                        $funcName = $token[1];
                        // Skip if it's a keyword or internal function
                        if (!in_array(strtolower($funcName), ['if', 'while', 'for', 'foreach', 'switch', 'return', 'echo', 'print', 'array', 'list', 'isset', 'empty', 'unset', 'die', 'exit', 'parent', 'self', 'static'])) {
                            if (!isset($functionCalls[$relativePath])) {
                                $functionCalls[$relativePath] = [];
                            }
                            $functionCalls[$relativePath][] = [
                                'name' => $funcName,
                                'line' => $lineNumber
                            ];
                        }
                    }
                    break;

                case T_VARIABLE:
                    $varName = $token[1];

                    // Track variable assignments for array type inference
                    $prevNonWhitespace = $i - 1;
                    while ($prevNonWhitespace >= 0 && (is_array($tokens[$prevNonWhitespace]) && $tokens[$prevNonWhitespace][0] === T_WHITESPACE)) {
                        $prevNonWhitespace--;
                    }

                    // Check if this is a variable assignment
                    if ($prevNonWhitespace >= 0 && is_array($tokens[$prevNonWhitespace]) && $tokens[$prevNonWhitespace][0] === T_VARIABLE) {
                        if (!isset($variableDeclarations[$relativePath])) {
                            $variableDeclarations[$relativePath] = [];
                        }
                        $variableDeclarations[$relativePath][] = [
                            'name' => $varName,
                            'line' => $lineNumber,
                            'context' => 'assignment'
                        ];
                    }

                    // Track array access patterns: $array['key'] or $array[$key]
                    $nextToken = $i + 1;
                    while (isset($tokens[$nextToken]) && is_array($tokens[$nextToken]) && $tokens[$nextToken][0] === T_WHITESPACE) {
                        $nextToken++;
                    }

                    if (isset($tokens[$nextToken]) && $tokens[$nextToken] === '[') {
                        if (!isset($arrayKeyAccesses[$relativePath])) {
                            $arrayKeyAccesses[$relativePath] = [];
                        }
                        $arrayKeyAccesses[$relativePath][] = [
                            'variable' => $varName,
                            'line' => $lineNumber
                        ];
                    }
                    break;
            }
        } elseif ($token === '{' || $token === '}') {
            $lineNumber++;
        } elseif ($token === "\n") {
            $lineNumber++;
        }
    }
    
    $definedInFile[$relativePath] = array_unique($definedInFile[$relativePath], SORT_REGULAR);
}

function extractDeclaration(array $tokens, int $startIndex, array $namespaces, string $type): ?string {
    $nextIndex = $startIndex + 1;
    while (isset($tokens[$nextIndex]) && is_array($tokens[$nextIndex]) && $tokens[$nextIndex][0] === T_WHITESPACE) {
        $nextIndex++;
    }
    
    if (!isset($tokens[$nextIndex]) || !is_array($tokens[$nextIndex]) || $tokens[$nextIndex][0] !== T_STRING) {
        return null;
    }
    
    return $tokens[$nextIndex][1];
}

function extractConstantDeclaration(array $tokens, int $startIndex): ?string {
    $nextIndex = $startIndex + 1;
    while (isset($tokens[$nextIndex]) && is_array($tokens[$nextIndex]) && $tokens[$nextIndex][0] === T_WHITESPACE) {
        $nextIndex++;
    }
    
    if (!isset($tokens[$nextIndex]) || !is_array($tokens[$nextIndex]) || $tokens[$nextIndex][0] !== T_STRING) {
        return null;
    }
    
    return $tokens[$nextIndex][1];
}

function extractAfterToken(array $tokens, int $startIndex): ?string {
    $nextIndex = $startIndex + 1;
    while (isset($tokens[$nextIndex]) && is_array($tokens[$nextIndex]) && $tokens[$nextIndex][0] === T_WHITESPACE) {
        $nextIndex++;
    }
    
    if (!isset($tokens[$nextIndex])) {
        return null;
    }
    
    if (is_array($tokens[$nextIndex]) && $tokens[$nextIndex][0] === T_STRING) {
        return $tokens[$nextIndex][1];
    }
    
    return null;
}

function extractCatchClass(array $tokens, int $startIndex): ?string {
    $j = $startIndex + 1;
    $foundParen = false;
    
    while (isset($tokens[$j])) {
        if ($tokens[$j] === '(') {
            $foundParen = true;
        } elseif ($foundParen && is_array($tokens[$j]) && $tokens[$j][0] === T_STRING) {
            return $tokens[$j][1];
        } elseif ($foundParen && $tokens[$j] === ')') {
            break;
        }
        $j++;
    }
    
    return null;
}

function extractIncludePath(array $tokens, int $startIndex): ?string {
    $pathTokens = [];
    $j = $startIndex + 1;
    
    while (isset($tokens[$j]) && $tokens[$j] !== ';') {
        if (is_array($tokens[$j]) && $tokens[$j][0] === T_ENCAPSED_AND_WHITESPACE) {
            $pathTokens[] = $tokens[$j][1];
        } elseif (is_array($tokens[$j]) && $tokens[$j][0] === T_CONSTANT_ENCAPSED_STRING) {
            $pathTokens[] = trim($tokens[$j][1], '"\'');
        } elseif ($tokens[$j] === "\n") {
            break;
        }
        $j++;
    }
    
    return $pathTokens ? implode('', $pathTokens) : null;
}

function isInternalClass(string $className): bool {
    $internalClasses = [
        'Exception', 'Error', 'Throwable', 'ArgumentCountError', 'ArithmeticError',
        'AssertionError', 'DivisionByZeroError', 'ErrorException', 'CompileError',
        'ParseError', 'TypeError', 'UnhandledMatchError', 'ValueError',
        'DateTime', 'DateTimeImmutable', 'DateInterval', 'DatePeriod',
        'DOMDocument', 'DOMNode', 'DOMElement', 'DOMNodeList',
        'PDO', 'PDOStatement', 'PDOException',
        'mysqli', 'mysqli_result', 'mysqli_stmt', 'mysqli_sql_exception',
        'ArrayObject', 'ArrayIterator', 'IteratorIterator', 'Traversable',
        'Countable', 'Serializable', 'JsonSerializable',
        'Stringable', 'Stringable', 'UnitEnum', 'BackedEnum',
        'Closure', 'Generator', 'Fiber', 'WeakReference',
        'SplFileInfo', 'SplFileObject', 'SplTempFileObject', 'SplDoublyLinkedList',
        'SplQueue', 'SplStack', 'SplHeap', 'SplMinHeap', 'SplMaxHeap',
        'SplPriorityQueue', 'SplFixedArray', 'SplObjectStorage',
        'RegexIterator', 'RecursiveRegexIterator', 'RecursiveIteratorIterator',
        'RecursiveDirectoryIterator', 'RecursiveIterator', 'RecursiveTreeIterator',
        'DirectoryIterator', 'FilesystemIterator', 'GlobIterator',
        'SimpleXMLElement', 'SimpleXMLIterator',
        'SoapClient', 'SoapServer', 'SoapFault',
        'XMLReader', 'XMLWriter',
        'ZipArchive', ' PharData', 'PharFileInfo', 'PharException',
        'Laminas\\Escaper\\Escaper', 'Psr\\Container\\ContainerInterface',
        'WP_Error', 'WP_Post', 'WP_User', 'WP_Query', 'WP_Term', 'WP_Object_Cache',
        'WP_Theme', 'WP_Screen', 'WP_List_Table', 'WP_Comment_Query',
        'WC_Product', 'WC_Order', 'WC_Customer', 'WC_Cart', 'WC_Session',
        'WP_REST_Request', 'WP_REST_Response', 'WP_REST_Server',
        'WP_Block_Editor_Context', 'WP_Block_Type', 'WP_Block', 'WP_Block_List'
    ];
    
    $shortName = substr($className, strrpos($className, '\\') + 1);
    return in_array($className, $internalClasses) || in_array($shortName, $internalClasses);
}

function isInternalFunction(string $funcName): bool {
    $internalFuncs = [
        'strlen', 'strcmp', 'strncmp', 'strcasecmp', 'strncasecmp', 'strpos', 'stripos',
        'strrpos', 'strripos', 'strstr', 'stristr', 'strrchr', 'substr', 'str_replace',
        'str_ireplace', 'strtr', 'trim', 'ltrim', 'rtrim', 'str_pad', 'str_repeat',
        'implode', 'explode', 'join', 'split', 'htmlspecialchars', 'htmlentities',
        'htmlspecialchars_decode', 'html_entity_decode', 'addslashes', 'stripslashes',
        'stripcslashes', 'strip_tags', 'strval', 'intval', 'floatval', 'doubleval',
        'boolval', 'settype', 'gettype', 'is_array', 'is_bool', 'is_float', 'is_int',
        'is_null', 'is_numeric', 'is_string', 'is_object', 'is_resource', 'is_scalar',
        'isset', 'empty', 'unset', 'defined', 'define', 'constant', 'time', 'mktime',
        'gmmktime', 'date', 'gmdate', 'date_default_timezone_set', 'date_default_timezone_get',
        'idate', 'getdate', 'localtime', 'checkdate', 'strtotime', 'strftime', 'gmstrftime',
        'array', 'array_key_exists', 'array_keys', 'array_values', 'array_count_values',
        'array_push', 'array_pop', 'array_shift', 'array_unshift', 'array_merge',
        'array_merge_recursive', 'array_replace', 'array_replace_recursive', 'array_slice',
        'array_splice', 'array_intersect', 'array_intersect_key', 'array_intersect_ukey',
        'array_uintersect', 'array_diff', 'array_diff_key', 'array_diff_ukey',
        'array_udiff', 'array_filter', 'array_map', 'array_reduce', 'array_walk',
        'array_walk_recursive', 'sort', 'rsort', 'asort', 'arsort', 'ksort', 'krsort',
        'natsort', 'natcasesort', 'usort', 'uasort', 'uksort', 'shuffle', 'array_sum',
        'array_product', 'array_reduce', 'array_reverse', 'array_pad', 'array_flip',
        'array_change_key_case', 'array_chunk', 'array_combine', 'array_column',
        'preg_match', 'preg_match_all', 'preg_replace', 'preg_replace_callback',
        'preg_split', 'preg_quote', 'preg_grep', 'preg_last_error', 'preg_last_error_msg',
        'mb_strlen', 'mb_strpos', 'mb_strrpos', 'mb_stripos', 'mb_strripos', 'mb_substr',
        'mb_substr_count', 'mb_strtolower', 'mb_strtoupper', 'mb_ucwords', 'mb_convert_case',
        'json_encode', 'json_decode', 'json_last_error', 'json_last_error_msg',
        'json_validate', 'serialize', 'unserialize', 'var_export', 'var_dump', 'print_r',
        'debug_zval_dump', 'debug_backtrace', 'debug_print_backtrace', 'error_reporting',
        'error_log', 'error_get_last', 'trigger_error', 'user_error', 'set_error_handler',
        'restore_error_handler', 'set_exception_handler', 'restore_exception_handler',
        'header_remove', 'headers_list', 'headers_sent', 'header', 'http_response_code',
        'connection_aborted', 'connection_status', 'ignore_user_abort', 'set_time_limit',
        'get_magic_quotes_gpc', 'get_magic_quotes_runtime', 'get_loaded_extensions',
        'extension_loaded', 'get_defined_functions', 'get_defined_constants', 'get_defined_vars',
        'func_num_args', 'func_get_arg', 'func_get_args', 'create_function', 'call_user_func',
        'call_user_func_array', 'forward_static_call', 'forward_static_call_array',
        'register_shutdown_function', 'register_tick_function', 'unregister_tick_function',
        'is_callable', 'method_exists', 'property_exists', 'class_exists', 'interface_exists',
        'trait_exists', 'function_exists', 'class_alias', 'get_declared_classes',
        'get_declared_interfaces', 'get_declared_traits', 'get_defined_functions',
        'get_defined_constants', 'gc_enabled', 'gc_collect_cycles', 'gc_status',
        'memory_get_usage', 'memory_get_peak_usage', 'memory_reset_peak_usage',
        'sys_getloadavg', 'sys_get_temp_dir', 'realpath_cache_size', 'realpath_cache_get',
        'opcache_get_status', 'opcache_compile_file', 'opcache_is_script_cached',
        'version_compare', 'php_uname', 'phpversion', 'phpinfo', 'phpcredits',
        'php_sapi_name', 'php_ini_loaded_file', 'php_ini_scanned_files',
        'rand', 'srand', 'mt_rand', 'mt_srand', 'random_int', 'random_bytes',
        'bin2hex', 'hex2bin', 'pack', 'unpack', 'md5', 'md5_file', 'sha1', 'sha1_file',
        'hash', 'hash_file', 'hash_hmac', 'hash_hmac_file', 'hash_algos', 'hash_init',
        'hash_update', 'hash_update_stream', 'hash_final', 'password_hash',
        'password_verify', 'password_needs_rehash', 'password_get_info',
        'base64_encode', 'base64_decode', 'convert_uuencode', 'convert_uudecode',
        'addcslashes', 'stripcslashes', 'quoted_printable_encode', 'quoted_printable_decode',
        'str_rot13', 'stream_context_create', 'stream_context_get_params',
        'stream_context_get_default', 'stream_context_set_option', 'stream_context_set_params',
        'stream_filter_prepend', 'stream_filter_append', 'stream_filter_remove',
        'stream_socket_client', 'stream_socket_server', 'stream_socket_accept',
        'stream_socket_get_name', 'stream_socket_recvfrom', 'stream_socket_sendto',
        'stream_get_contents', 'stream_get_line', 'stream_get_meta_data', 'stream_get_transports',
        'stream_get_wrappers', 'stream_resolve_include_path', 'stream_wrapper_register',
        'stream_wrapper_unregister', 'stream_wrapper_restore', 'file_get_contents',
        'file_put_contents', 'file', 'fopen', 'fclose', 'fread', 'fwrite', 'fgets',
        'fgetss', 'file', 'readfile', 'fscanf', 'fseek', 'ftell', 'rewind', 'feof',
        'fflush', 'ftruncate', 'flock', 'fnmatch', 'glob', 'is_file', 'is_dir', 'is_link',
        'is_executable', 'is_readable', 'is_writable', 'is_uploaded_file', 'tempnam',
        'tmpfile', 'realpath', 'dirname', 'basename', 'pathinfo', 'parse_url', 'urldecode',
        'urlencode', 'rawurlencode', 'rawurldecode', 'http_build_query', 'parse_str',
        'str_getcsv', 'fputcsv', 'fgetcsv', 'strtok', 'chunk_split', 'nl2br',
        'filter_var', 'filter_input', 'filter_list', 'filter_id', 'filter_has_var',
        'hash_equals', 'crypt', 'wordwrap', 'count_chars', 'similar_text', 'levenshtein',
        'soundex', 'metaphone', 'abs', 'ceil', 'floor', 'round', 'max', 'min', 'pow',
        'sqrt', 'log', 'log10', 'log1p', 'exp', 'expm1', 'pi', 'is_nan', 'is_finite',
        'is_infinite', 'hypot', 'deg2rad', 'rad2deg', 'intdiv', 'fmod', 'intdiv',
        'wp_die', 'wp_enqueue_script', 'wp_enqueue_style', 'wp_register_script',
        'wp_register_style', 'wp_add_inline_script', 'wp_add_inline_style',
        'wp_localize_script', 'wp_scripts', 'wp_styles', 'get_option', 'update_option',
        'add_option', 'delete_option', 'get_post_meta', 'update_post_meta', 'delete_post_meta',
        'get_user_meta', 'update_user_meta', 'delete_user_meta', 'get_term_meta',
        'update_term_meta', 'delete_term_meta', 'get_posts', 'get_post', 'wp_insert_post',
        'wp_update_post', 'wp_delete_post', 'get_pages', 'get_page_by_path',
        'wp_get_current_user', 'wp_get_current_user', 'wp_get_current_user',
        'current_user_can', 'user_can', 'is_user_logged_in', 'wp_logout', 'wp_login',
        'wp_authenticate', 'sanitize_text_field', 'sanitize_email', 'sanitize_title',
        'sanitize_file_name', 'esc_html', 'esc_attr', 'esc_url', 'esc_js', 'esc_textarea',
        'wp_kses', 'wp_kses_post', 'wp_filter_post_kses', 'wp_kses_hair',
        'add_action', 'do_action', 'do_action_ref_array', 'has_action', 'remove_action',
        'remove_all_actions', 'doing_action', 'add_filter', 'apply_filters',
        'apply_filters_ref_array', 'has_filter', 'remove_filter', 'remove_all_filters',
        'doing_filter', 'current_action', 'current_filter',
        'add_shortcode', 'remove_shortcode', 'remove_all_shortcodes', 'shortcode_atts',
        'do_shortcode', 'strip_shortcodes', 'has_shortcode',
        'wp_remote_get', 'wp_remote_post', 'wp_remote_head', 'wp_remote_request',
        'wp_remote_retrieve_body', 'wp_remote_retrieve_headers', 'wp_remote_retrieve_response_code',
        'wp_remote_retrieve_response_message', 'wp_remote_post', 'wp_remote_get',
        'is_wp_error', 'is_admin', 'is_front_page', 'is_home', 'is_single', 'is_page',
        'is_singular', 'is_archive', 'is_search', 'is_404', 'is_user_logged_in',
        'wp_verify_nonce', 'wp_create_nonce', 'wp_nonce_field', 'wp_nonce_url',
        'check_admin_referer', 'check_ajax_referer', 'wp_salt', 'wp_hash',
        'wp_generate_password', 'wp_rand', 'wp_kses_normalize_entities', 'wp_kses_bad_protocol',
        'wp_kses_array_lc', 'wp_iso_descr', 'wp_kses_split',
        'WC', 'wc_get_product', 'wc_get_products', 'wc_get_order', 'wc_get_orders',
        'wc_add_order_item', 'wc_update_order_item', 'wc_delete_order_item',
        'wc_add_order_item_meta', 'wc_delete_order_item_meta',
        'wc_get_customer', 'wc_update_customer', 'wc_add_customer_note',
        'wc_create_nonce', 'wc_rest_prepare_product', 'wc_rest_prepare_order'
    ];
    
    $shortName = strtolower($funcName);
    $parts = explode('\\', $funcName);
    $shortName = strtolower(end($parts));
    
    return in_array(strtolower($funcName), $internalFuncs) || in_array($shortName, $internalFuncs);
}

$foundIssues = false;

function reportIssues(array $issues, string $type): bool {
    global $foundIssues, $verbose;
    
    if (empty($issues)) {
        return false;
    }
    
    $foundIssues = true;
    $unique = [];
    
    foreach ($issues as $issue) {
        $key = $issue['name'] ?? $issue['path'] ?? json_encode($issue);
        if (!isset($unique[$key])) {
            $unique[$key] = $issue;
        }
    }
    
    foreach ($unique as $issue) {
        if (isset($issue['path'])) {
            echo "DUPLICATE {$type}: '{$issue['path']}' declared multiple times:\n";
            foreach ($issue['locations'] ?? [] as $loc) {
                echo "    {$loc['file']}:{$loc['line']}\n";
            }
        } elseif (isset($issue['name'])) {
            echo "MISSING {$type}: '{$issue['name']}' referenced but not declared:\n";
            echo "    {$issue['file']}:{$issue['line']}\n";
        } else {
            echo "ISSUE {$type}: " . json_encode($issue) . "\n";
        }
    }
    
    return true;
}

if ($verbose) {
    echo "Scanned {$stats['files']} files\n";
    echo "Found {$stats['classes']} classes, {$stats['interfaces']} interfaces, ";
    echo "{$stats['traits']} traits, {$stats['functions']} functions, {$stats['constants']} constants\n";
    echo "Found {$stats['includes']} include/require statements\n\n";
}

$anyIssues = false;

foreach ($declarations as $type => $decls) {
    foreach ($decls as $name => $locations) {
        if (count($locations) > 1) {
            $anyIssues = true;
            echo "DUPLICATE {$type}: '{$name}' declared in " . count($locations) . " locations:\n";
            foreach ($locations as $loc) {
                echo "    {$loc['file']}:{$loc['line']}\n";
            }
        }
    }
}

foreach ($typeHints as $className => $usages) {
    $declared = isset($declarations['classes'][$className]) ||
                isset($declarations['interfaces'][$className]) ||
                isset($declarations['traits'][$className]);
    
    if (!$declared && !isInternalClass($className)) {
        $anyIssues = true;
        $unique = [];
        foreach ($usages as $usage) {
            $key = "{$usage['file']}:{$usage['line']}";
            if (!isset($unique[$key])) {
                $unique[$key] = $usage;
            }
        }
        echo "MISSING CLASS/TYPE: '{$className}' used but not declared:\n";
        foreach ($unique as $usage) {
            echo "    {$usage['file']}:{$usage['line']} ({$usage['usage']})\n";
        }
    }
}

foreach ($iterator as $file) {
    if ($file->getExtension() !== 'php') {
        continue;
    }
    
    $filePath = $file->getPathname();
    if (shouldExclude($filePath, $excludeDirs)) {
        continue;
    }
    
    $content = file_get_contents($filePath);
    if ($content === false) {
        continue;
    }
    
    $lines = explode("\n", $content);
    $relativePath = str_replace($pluginDir . '/', '', $filePath);
    
    $activationHooks = [];
    foreach ($lines as $lineNum => $line) {
        $lineNum += 1;
        
        if (preg_match('/register_activation_hook\s*\(/i', $line)) {
            $activationHooks[] = [
                'line' => $lineNum,
                'content' => $line
            ];
        }
        
        if (preg_match('/(include|require|fopen|file_get_contents|file_put_contents|is_file|is_dir|file_exists)\s*\(\s*[\'\"]([\/]|Windows|C:|D:|E:)/i', $line)) {
            if (!preg_match('/ABSPATH|WPINC|WP_CONTENT_DIR|WP_PLUGIN_DIR|WPMU_PLUGIN_DIR|WP_CONTENT_URL|WP_PLUGIN_URL|WPMU_PLUGIN_URL/i', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'hardcoded_absolute_path',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Hardcoded absolute path detected. Use WordPress path functions.'
                ];
            }
        }
        
        if (preg_match('/\$[a-zA-Z_]+\s*\.\s*[\'\"]([\/\\]?)[a-zA-Z_]/', $line)) {
            if (!preg_match('/DIRECTORY_SEPARATOR|DS|plugin_dir_path|template_directory|get_template_directory|dirname\(realpath/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'missing_separator',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Path concatenation without proper directory separator.'
                ];
            }
        }
        
        if (preg_match('/(plugin_dir_path|plugin_dir_url|template_directory|get_template_directory).*__FILE__/', $line) && !preg_match('/__FILE__.*dirname/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'incorrect_file_usage',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => '__FILE__ returns the file path. Use dirname(__FILE__) for directory paths.'
            ];
        }
        
        if (preg_match('/(include|require|fopen|file_get_contents|file_put_contents|unlink|mkdir|rmdir)\s*\(\s*(\$[_A-Z]+|\$_(GET|POST|REQUEST|COOKIE))/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'path_traversal_risk',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => 'Direct user input used in file operation. Must validate and sanitize.'
            ];
        }
        
        if (preg_match('/(include|require)\s*\(\s*[\'\"][^\'\"]*\.(php|inc)/', $line)) {
            if (!preg_match('/plugin_dir_path|plugin_dir_url|locate_template|get_template_directory|get_stylesheet_directory|dirname\(__FILE__\)|ABSPATH/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'missing_wp_path_function',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Use plugin_dir_path(), template_directory(), or similar WP functions.'
                ];
            }
        }
        
        if (preg_match('/[A-Za-z]:\\\\\\\/', $line) && !preg_match('/DIRECTORY_SEPARATOR|DS|dirname|realpath|ABSPATH/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'windows_backslash',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => 'Use forward slashes or DIRECTORY_SEPARATOR constant for cross-platform compatibility.'
            ];
        }
        
        if (preg_match('/\$[a-zA-Z_]+\s*=\s*\$[a-zA-Z_]+\s*\.\s*[\'\"]?\s*[\'\"]?\s*;\s*\$[a-zA-Z_]+/', $line) && preg_match('/(path|dir|file)/i', $line)) {
            if (!preg_match('/DIRECTORY_SEPARATOR|DS|\/.*\//|\\\.*\\/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'incorrect_path_concat',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Incorrect path concatenation. Missing directory separator.'
                ];
            }
        }
    }
    
    if (!empty($activationHooks)) {
        foreach ($activationHooks as $hook) {
            $hookLine = $hook['line'];
            
            if (preg_match('/\[\s*(\$[a-zA-Z_]+|new\s+[A-Z][a-zA-Z0-9_]+\(\))\s*,\s*[\'\"](install|activate)[\'\"]/', $hook['content'])) {
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class.*install|require|include|autoload/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'install_class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => 'register_activation_hook references install class/object but class may not be loaded before hook registration.'
                    ];
                }
            }
            
            if (preg_match('/\[\s*[\'\"]([A-Z][a-zA-Z0-9_]*)[\'\"]\s*,\s*[\'\"](install|activate)[\'\"]/', $hook['content'], $matches)) {
                $className = $matches[1];
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class\s+' . preg_quote($className, '/') . '|require.*' . preg_quote($className, '/') . '|include.*' . preg_quote($className, '/') . '/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => "register_activation_hook references class '{$className}' but class file may not be loaded before hook registration."
                    ];
                }
            }
            
            if (preg_match('/([A-Z][a-zA-Z0-9_]*)::(install|activate)/', $hook['content'], $matches)) {
                $className = $matches[1];
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class\s+' . preg_quote($className, '/') . '|require.*' . preg_quote($className, '/') . '/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'static_class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => "register_activation_hook references static class '{$className}' but class may not be loaded before hook registration."
                    ];
                }
            }
        }
    }

    if (!empty($issues['file_paths'])) {
        echo "\nFILE PATH VALIDATION ISSUES:\n";
        $uniquePathIssues = [];
        
        foreach ($issues['file_paths'] as $issue) {
            $key = "{$issue['type']}:{$issue['file']}:{$issue['line']}";
            if (!isset($uniquePathIssues[$key])) {
                $uniquePathIssues[$key] = $issue;
            }
        }
        
        foreach ($uniquePathIssues as $issue) {
            $anyIssues = true;
            echo "  FILE PATH ISSUE in {$issue['file']}:{$issue['line']}:\n";
            echo "    {$issue['message']}\n";
        }
    }
    
    if (!empty($issues['activation'])) {
        echo "\nACTIVATION HOOK VALIDATION ISSUES:\n";
        $uniqueActivationIssues = [];
        
        foreach ($issues['activation'] as $issue) {
            $key = "{$issue['type']}:{$issue['file']}:{$issue['line']}";
            if (!isset($uniqueActivationIssues[$key])) {
                $uniqueActivationIssues[$key] = $issue;
            }
        }
        
        foreach ($uniqueActivationIssues as $issue) {
            $anyIssues = true;
            echo "  ACTIVATION HOOK ISSUE in {$issue['file']}:{$issue['line']}:\n";
            echo "    {$issue['message']}\n";
        }
    }

    // Now run the additional enhanced runtime checks (undefined functions, early calls, arrays, css overlaps)

    echo "Checking for undefined functions...\n";
    $undefinedFunctionsFound = false;

    foreach ($functionCalls as $fFile => $calls) {
        foreach ($calls as $call) {
            $funcName = $call['name'];

            if (isInternalFunction($funcName)) {
                continue;
            }

            // Check definition in same file
            $definedInThisFile = isset($functionDefinitions[$fFile][$funcName]);

            // Check global implementations
            $isGloballyDefined = false;
            foreach ($functionImplementations as $fullName => $impls) {
                $parts = explode('\\', $fullName);
                $shortName = end($parts);
                if (strtolower($shortName) === strtolower($funcName)) {
                    $isGloballyDefined = true;
                    break;
                }
            }

            if (!$definedInThisFile && !$isGloballyDefined) {
                $undefinedFunctionsFound = true;
                echo "UNDEFINED FUNCTION: '{$funcName}' called at {$fFile}:{$call['line']}\n";
                $issues['undefined_functions'][] = [
                    'name' => $funcName,
                    'file' => $fFile,
                    'line' => $call['line']
                ];
            }
        }
    }

    if (!$undefinedFunctionsFound) {
        echo "  ✓ No undefined functions found\n";
    }

    echo "\nChecking for early function calls...\n";
    $earlyCallsFound = false;

    foreach ($functionCalls as $fFile => $calls) {
        if (!isset($functionDefinitions[$fFile])) continue;
        foreach ($calls as $call) {
            $funcName = $call['name'];
            $callLine = $call['line'];
            $defLine = null;
            foreach ($functionDefinitions[$fFile] as $name => $line) {
                if (strtolower($name) === strtolower($funcName)) { $defLine = $line; break; }
            }
            if ($defLine === null) continue;
            if ($callLine > $defLine) continue;
            if ($callLine < $defLine - 2) {
                $earlyCallsFound = true;
                echo "EARLY FUNCTION CALL: '{$funcName}' called at line {$callLine} but defined at line {$defLine} in {$fFile}\n";
                $issues['early_function_calls'][] = [ 'name' => $funcName, 'file' => $fFile, 'call_line' => $callLine, 'def_line' => $defLine ];
            }
        }
    }

    if (!$earlyCallsFound) echo "  ✓ No early function calls found\n";

    echo "\nChecking for potential undefined arrays and array keys...\n";
    $undefinedArraysFound = false;

    $arrayInitializations = [];
    foreach ($variableDeclarations as $fFile => $vars) {
        foreach ($vars as $var) { $arrayInitializations[$fFile][] = $var['name']; }
    }

    foreach ($arrayKeyAccesses as $fFile => $accesses) {
        foreach ($accesses as $access) {
            $varName = $access['variable'];
            $isInitialized = isset($arrayInitializations[$fFile]) && in_array($varName, $arrayInitializations[$fFile]);
            if (!$isInitialized) {
                $undefinedArraysFound = true;
                echo "POTENTIAL UNDEFINED ARRAY: '\${$varName}' accessed as array at {$fFile}:{$access['line']}\n";
                $issues['undefined_arrays'][] = [ 'variable' => $varName, 'file' => $fFile, 'line' => $access['line'] ];
            }
        }
    }

    if (!$undefinedArraysFound) echo "  ✓ No potential undefined array issues found\n";

    // Collect CSS and check for overlaps
    $cssIterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($pluginDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::LEAVES_ONLY
    );

    foreach ($cssIterator as $cfile) {
        if ($cfile->getExtension() === 'css') {
            $cssFile = $cfile->getPathname();
            if (!shouldExclude($cssFile, $excludeDirs)) {
                $relativePath = str_replace($pluginDir . '/', '', $cssFile);
                $cssFiles[$relativePath] = file_get_contents($cssFile);
            }
        }
    }

    if (empty($cssFiles)) {
        echo "  ℹ No CSS files found to check for overlaps\n";
    } else {
        $overlapPatterns = [
            'position:\s*absolute\s*;\s*z-index:\s*0|z-index:\s*0\s*;\s*position:\s*absolute' => 'Absolute positioned element at z-index 0 (may overlap)',
            'position:\s*absolute\s*;[^}]*top:\s*0[^}]*bottom:\s*0' => 'Absolute element anchored to both top and bottom',
            'position:\s*absolute\s*;[^}]*left:\s*0[^}]*right:\s*0' => 'Absolute element anchored to both left and right',
            'position:\s*fixed\s*;[^}]*top:\s*0[^}]*bottom:\s*0' => 'Fixed element anchored to both top and bottom',
            'position:\s*fixed\s*;[^}]*left:\s*0[^}]*right:\s*0' => 'Fixed element anchored to both left and right',
            'margin-left:\s*-\d+px|margin-top:\s*-\d+px|margin-right:\s*-\d+px|margin-bottom:\s*-\d+px' => 'Negative margin (may cause overlap)',
        ];

        foreach ($cssFiles as $cssPath => $cssContent) {
            foreach ($overlapPatterns as $pattern => $description) {
                if (preg_match_all('/' . $pattern . '/si', $cssContent, $matches, PREG_OFFSET_CAPTURE)) {
                    $lines = explode("\n", $cssContent);
                    foreach ($matches[0] as $match) {
                        $offset = $match[1];
                        $lineNumber = substr_count(substr($cssContent, 0, $offset), "\n") + 1;
                        echo "POTENTIAL CSS OVERLAP in {$cssPath}:{$lineNumber}\n";
                        echo "    Reason: {$description}\n";
                        echo "    Context: " . substr($match[0], 0, 100) . "...\n";
                        $issues['css_overlaps'][] = [ 'file' => $cssPath, 'line' => $lineNumber, 'pattern' => $pattern, 'description' => $description ];
                    }
                }
            }
        }

        if (empty($issues['css_overlaps'])) {
            echo "  ✓ No potential CSS overlaps detected\n";
        }
    }

    echo "\n";

    // SUMMARY: report counts and decide exit status
    $totalIssues = count($issues['undefined_functions']) + count($issues['early_function_calls']) + count($issues['undefined_arrays']) + count($issues['css_overlaps']);

    if ($anyIssues || $totalIssues > 0) {
        echo "\n" . str_repeat("=", 70) . "\n";
        echo "VALIDATION SUMMARY\n";
        echo str_repeat("=", 70) . "\n";

        if ($anyIssues) {
            echo "Runtime issues (duplicates, missing classes): FOUND\n";
        }

        if (count($issues['undefined_functions']) > 0) {
            echo "Undefined functions: " . count($issues['undefined_functions']) . " found\n";
        }

        if (count($issues['early_function_calls']) > 0) {
            echo "Early function calls: " . count($issues['early_function_calls']) . " found\n";
        }

        if (count($issues['undefined_arrays']) > 0) {
            echo "Potential undefined arrays: " . count($issues['undefined_arrays']) . " found\n";
        }

        if (count($issues['css_overlaps']) > 0) {
            echo "Potential CSS overlaps: " . count($issues['css_overlaps']) . " found\n";
        }

        echo "\nFAIL: Issues detected that should be reviewed\n";
        exit(1);
    }

    // If we got here, no issues
    if (!$quiet) {
        if ($verbose) {
            echo "No runtime error issues, file path problems, or activation hook issues found across {$stats['files']} files.\n";
            echo "✓ No undefined functions\n";
            echo "✓ No early function calls\n";
            echo "✓ No potential CSS overlaps\n";
        } else {
            echo "PASS: No issues found.\n";
        }
    }

        
        if (preg_match('/(include|require|fopen|file_get_contents|file_put_contents|is_file|is_dir|file_exists)\s*\(\s*[\'"]([\/]|Windows|C:|D:|E:)/i', $line)) {
            if (!preg_match('/ABSPATH|WPINC|WP_CONTENT_DIR|WP_PLUGIN_DIR|WPMU_PLUGIN_DIR|WP_CONTENT_URL|WP_PLUGIN_URL|WPMU_PLUGIN_URL/i', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'hardcoded_absolute_path',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Hardcoded absolute path detected. Use WordPress path functions.'
                ];
            }
        }
        
        if (preg_match('/\$[a-zA-Z_]+\s*\.\s*[\'"]([\/\\]?)[a-zA-Z_]/', $line)) {
            if (!preg_match('/DIRECTORY_SEPARATOR|DS|plugin_dir_path|template_directory|get_template_directory|dirname\(realpath/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'missing_separator',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Path concatenation without proper directory separator.'
                ];
            }
        }
        
        if (preg_match('/(plugin_dir_path|plugin_dir_url|template_directory|get_template_directory).*__FILE__/', $line) && !preg_match('/__FILE__.*dirname/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'incorrect_file_usage',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => '__FILE__ returns the file path. Use dirname(__FILE__) for directory paths.'
            ];
        }
        
        if (preg_match('/(include|require|fopen|file_get_contents|file_put_contents|unlink|mkdir|rmdir)\s*\(\s*(\$[_A-Z]+|\$_(GET|POST|REQUEST|COOKIE))/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'path_traversal_risk',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => 'Direct user input used in file operation. Must validate and sanitize.'
            ];
        }
        
        if (preg_match('/(include|require)\s*\(\s*[\'"][^\'"]*\.(php|inc)/', $line)) {
            if (!preg_match('/plugin_dir_path|plugin_dir_url|locate_template|get_template_directory|get_stylesheet_directory|dirname\(__FILE__\)|ABSPATH/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'missing_wp_path_function',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Use plugin_dir_path(), template_directory(), or similar WP functions.'
                ];
            }
        }
        
        if (preg_match('/[A-Za-z]:\\\\\\\\/', $line) && !preg_match('/DIRECTORY_SEPARATOR|DS|dirname|realpath|ABSPATH/', $line)) {
            $issues['file_paths'][] = [
                'type' => 'windows_backslash',
                'file' => $relativePath,
                'line' => $lineNum,
                'message' => 'Use forward slashes or DIRECTORY_SEPARATOR constant for cross-platform compatibility.'
            ];
        }
        
        if (preg_match('/\$[a-zA-Z_]+\s*=\s*\$[a-zA-Z_]+\s*\.\s*[\'"]?\s*[\'"]?\s*;\s*\$[a-zA-Z_]+/', $line) && preg_match('/(path|dir|file)/i', $line)) {
            if (!preg_match('/DIRECTORY_SEPARATOR|DS|\/.*\/|\\\\.*\\\\/', $line)) {
                $issues['file_paths'][] = [
                    'type' => 'incorrect_path_concat',
                    'file' => $relativePath,
                    'line' => $lineNum,
                    'message' => 'Incorrect path concatenation. Missing directory separator.'
                ];
            }
        }
    }
    
    if (!empty($activationHooks)) {
        foreach ($activationHooks as $hook) {
            $hookLine = $hook['line'];
            
            if (preg_match('/\[\s*(\$[a-zA-Z_]+|new\s+[A-Z][a-zA-Z0-9_]+\(\))\s*,\s*[\'"](install|activate)[\'"]/', $hook['content'])) {
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class.*install|require|include|autoload/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'install_class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => 'register_activation_hook references install class/object but class may not be loaded before hook registration.'
                    ];
                }
            }
            
            if (preg_match('/\[\s*[\'"]([A-Z][a-zA-Z0-9_]*)[\'"]\s*,\s*[\'"](install|activate)[\'"]/', $hook['content'], $matches)) {
                $className = $matches[1];
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class\s+' . preg_quote($className, '/') . '|require.*' . preg_quote($className, '/') . '|include.*' . preg_quote($className, '/') . '/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => "register_activation_hook references class '{$className}' but class file may not be loaded before hook registration."
                    ];
                }
            }
            
            if (preg_match('/([A-Z][a-zA-Z0-9_]*)::(install|activate)/', $hook['content'], $matches)) {
                $className = $matches[1];
                $priorLines = array_slice($lines, max(0, $hookLine - 25), min(25, $hookLine));
                $priorContent = implode("\n", $priorLines);
                
                if (!preg_match('/class\s+' . preg_quote($className, '/') . '|require.*' . preg_quote($className, '/') . '/si', $priorContent)) {
                    $issues['activation'][] = [
                        'type' => 'static_class_not_loaded',
                        'file' => $relativePath,
                        'line' => $hookLine,
                        'message' => "register_activation_hook references static class '{$className}' but class may not be loaded before hook registration."
                    ];
                }
            }
        }
    }
}

foreach ($cssIterator as $file) {
    if ($file->getExtension() === 'css') {
        $cssFile = $file->getPathname();
        if (!shouldExclude($cssFile, $excludeDirs)) {
            $relativePath = str_replace($pluginDir . '/', '', $cssFile);
            $cssFiles[$relativePath] = file_get_contents($cssFile);
        }
    }
}

if (empty($cssFiles)) {
    echo "  ℹ No CSS files found to check for overlaps\n";
} else {
    // Check for CSS patterns that could cause overlapping elements
    $overlapPatterns = [
        'position:\s*absolute\s*;\s*z-index:\s*0|z-index:\s*0\s*;\s*position:\s*absolute' => 'Absolute positioned element at z-index 0 (may overlap)',
        'position:\s*absolute\s*;[^}]*top:\s*0[^}]*bottom:\s*0' => 'Absolute element anchored to both top and bottom',
        'position:\s*absolute\s*;[^}]*left:\s*0[^}]*right:\s*0' => 'Absolute element anchored to both left and right',
        'position:\s*fixed\s*;[^}]*top:\s*0[^}]*bottom:\s*0' => 'Fixed element anchored to both top and bottom',
        'position:\s*fixed\s*;[^}]*left:\s*0[^}]*right:\s*0' => 'Fixed element anchored to both left and right',
        'margin-left:\s*-\d+px|margin-top:\s*-\d+px|margin-right:\s*-\d+px|margin-bottom:\s*-\d+px' => 'Negative margin (may cause overlap)',
    ];

    foreach ($cssFiles as $cssPath => $cssContent) {
        foreach ($overlapPatterns as $pattern => $description) {
            if (preg_match_all('/' . $pattern . '/si', $cssContent, $matches, PREG_OFFSET_CAPTURE)) {
                $lines = explode("\n", $cssContent);
                foreach ($matches[0] as $match) {
                    $offset = $match[1];
                    $lineNumber = substr_count(substr($cssContent, 0, $offset), "\n") + 1;
                    $undefinedArraysFound = true;
                    echo "POTENTIAL CSS OVERLAP in {$cssPath}:{$lineNumber}\n";
                    echo "    Reason: {$description}\n";
                    echo "    Context: " . substr($match[0], 0, 100) . "...\n";
                    $issues['css_overlaps'][] = [
                        'file' => $cssPath,
                        'line' => $lineNumber,
                        'pattern' => $pattern,
                        'description' => $description
                    ];
                }
            }
        }
    }

}

if (!empty($issues['file_paths'])) {
    echo "\nFILE PATH VALIDATION ISSUES:\n";
    $uniquePathIssues = [];
    
    foreach ($issues['file_paths'] as $issue) {
        $key = "{$issue['type']}:{$issue['file']}:{$issue['line']}";
        if (!isset($uniquePathIssues[$key])) {
            $uniquePathIssues[$key] = $issue;
        }
    }
    
    foreach ($uniquePathIssues as $issue) {
        $anyIssues = true;
        echo "  FILE PATH ISSUE in {$issue['file']}:{$issue['line']}:\n";
        echo "    {$issue['message']}\n";
    }
}

if (!empty($issues['activation'])) {
    echo "\nACTIVATION HOOK VALIDATION ISSUES:\n";
    $uniqueActivationIssues = [];
    
    foreach ($issues['activation'] as $issue) {
        $key = "{$issue['type']}:{$issue['file']}:{$issue['line']}";
        if (!isset($uniqueActivationIssues[$key])) {
            $uniqueActivationIssues[$key] = $issue;
        }
    }
    
    foreach ($uniqueActivationIssues as $issue) {
        $anyIssues = true;
        echo "  ACTIVATION HOOK ISSUE in {$issue['file']}:{$issue['line']}:\n";
        echo "    {$issue['message']}\n";
    }


    if (!$undefinedArraysFound) {
        echo "  ✓ No potential CSS overlaps detected\n";
    }
}
echo "\n";

// ============================================
// SUMMARY
// ============================================
$totalIssues = count($issues['undefined_functions']) +
                count($issues['early_function_calls']) +
                count($issues['undefined_arrays']) +
                count($issues['css_overlaps']);

if ($anyIssues || $totalIssues > 0) {
    echo "\n" . str_repeat("=", 70) . "\n";
    echo "VALIDATION SUMMARY\n";
    echo str_repeat("=", 70) . "\n";

    if ($anyIssues) {
        echo "Runtime issues (duplicates, missing classes): FOUND\n";
    }

    if (count($issues['undefined_functions']) > 0) {
        echo "Undefined functions: " . count($issues['undefined_functions']) . " found\n";
    }

    if (count($issues['early_function_calls']) > 0) {
        echo "Early function calls: " . count($issues['early_function_calls']) . " found\n";
    }

    if (count($issues['undefined_arrays']) > 0) {
        echo "Potential undefined arrays: " . count($issues['undefined_arrays']) . " found\n";
    }

    if (count($issues['css_overlaps']) > 0) {
        echo "Potential CSS overlaps: " . count($issues['css_overlaps']) . " found\n";
    }

    echo "\nFAIL: Issues detected that should be reviewed\n";
    exit(1);
}

if (!$anyIssues) {
    if (!$quiet) {
        if ($verbose) {
            echo "No runtime error issues, file path problems, or activation hook issues found across {$stats['files']} files.\n";
            echo "✓ No undefined functions\n";
            echo "✓ No early function calls\n";
            echo "✓ No potential CSS overlaps\n";
        } else {
            echo "PASS: No issues found.\n";
        }
    }
    exit(0);
}

exit(1);
