/**
 * Native Module Helper
 * Detects and auto-rebuilds native Node.js modules when they become
 * incompatible after a Node.js version update.
 */

import { execSync } from 'child_process';
import { dirname, join } from 'path';
import { existsSync } from 'fs';
import { logger } from './logger.js';

/**
 * Check if an error is a NODE_MODULE_VERSION mismatch error
 * @param {Error} error - The error to check
 * @returns {boolean} True if it's a version mismatch error
 */
export function isModuleVersionError(error) {
    const message = error?.message || '';
    return message.includes('NODE_MODULE_VERSION') &&
           message.includes('was compiled against a different Node.js version');
}

/**
 * Extract the module path from a NODE_MODULE_VERSION error message
 * @param {Error} error - The error containing the module path
 * @returns {string|null} The path to the .node file, or null if not found
 */
export function extractModulePath(error) {
    const message = error?.message || '';
    // Match pattern like: "The module '/path/to/module.node'"
    const match = message.match(/The module '([^']+\.node)'/);
    return match ? match[1] : null;
}

/**
 * Find the package root directory from a .node file path
 * @param {string} nodeFilePath - Path to the .node file
 * @returns {string|null} Path to the package root, or null if not found
 */
export function findPackageRoot(nodeFilePath) {
    // Walk up from the .node file to find package.json
    let dir = dirname(nodeFilePath);
    while (dir) {
        const packageJsonPath = join(dir, 'package.json');
        if (existsSync(packageJsonPath)) {
            return dir;
        }
        const parentDir = dirname(dir);
        // Stop when we've reached the filesystem root (dirname returns same path)
        if (parentDir === dir) {
            break;
        }
        dir = parentDir;
    }
    return null;
}

/**
 * Attempt to rebuild a native module
 * @param {string} packagePath - Path to the package root directory
 * @returns {boolean} True if rebuild succeeded, false otherwise
 */
export function rebuildModule(packagePath) {
    try {
        logger.info(`[NativeModule] Rebuilding native module at: ${packagePath}`);

        // Run npm rebuild in the package directory
        const output = execSync('npm rebuild', {
            cwd: packagePath,
            stdio: 'pipe', // Capture output instead of printing
            timeout: 120000 // 2 minute timeout
        });

        // Log rebuild output for debugging
        const outputStr = output?.toString().trim();
        if (outputStr) {
            logger.debug(`[NativeModule] Rebuild output:\n${outputStr}`);
        }

        logger.success('[NativeModule] Rebuild completed successfully');
        return true;
    } catch (error) {
        // Include stdout/stderr from the failed command for troubleshooting
        const stdout = error.stdout?.toString().trim();
        const stderr = error.stderr?.toString().trim();
        let errorDetails = `[NativeModule] Rebuild failed: ${error.message}`;
        if (stdout) {
            errorDetails += `\n[NativeModule] stdout: ${stdout}`;
        }
        if (stderr) {
            errorDetails += `\n[NativeModule] stderr: ${stderr}`;
        }
        logger.error(errorDetails);
        return false;
    }
}

/**
 * Attempt to auto-rebuild a native module from an error
 * @param {Error} error - The NODE_MODULE_VERSION error
 * @returns {boolean} True if rebuild succeeded, false otherwise
 */
export function attemptAutoRebuild(error) {
    const nodePath = extractModulePath(error);
    if (!nodePath) {
        logger.error('[NativeModule] Could not extract module path from error');
        return false;
    }

    const packagePath = findPackageRoot(nodePath);
    if (!packagePath) {
        logger.error('[NativeModule] Could not find package root');
        return false;
    }

    logger.warn('[NativeModule] Native module version mismatch detected');
    logger.info('[NativeModule] Attempting automatic rebuild...');

    return rebuildModule(packagePath);
}

/**
 * Recursively clear a module and its dependencies from the require cache
 * This is needed after rebuilding a native module to force re-import
 * @param {string} modulePath - Resolved path to the module
 * @param {object} cache - The require.cache object
 * @param {Set} [visited] - Set of already-visited paths to prevent cycles
 */
export function clearRequireCache(modulePath, cache, visited = new Set()) {
    if (visited.has(modulePath)) return;
    visited.add(modulePath);

    const mod = cache[modulePath];
    if (!mod) return;

    // Recursively clear children first
    if (mod.children) {
        for (const child of mod.children) {
            clearRequireCache(child.id, cache, visited);
        }
    }

    // Remove from parent's children array
    if (mod.parent && mod.parent.children) {
        const idx = mod.parent.children.indexOf(mod);
        if (idx !== -1) {
            mod.parent.children.splice(idx, 1);
        }
    }

    // Delete from cache
    delete cache[modulePath];
}

export default {
    isModuleVersionError,
    extractModulePath,
    findPackageRoot,
    rebuildModule,
    attemptAutoRebuild,
    clearRequireCache
};
