const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const {
    copyRuntimeToNativeBuild,
    findLibMpv,
    patchAddonForBundledRuntime,
    validateNoForbiddenRuntimeLinks,
} = require('../../tools/packaging/embedded-mpv-macos.cjs');

const workspaceRoot = process.cwd();
const addonRoot = path.join(workspaceRoot, 'apps', 'electron-backend', 'native');
const outputDir = path.join(addonRoot, 'build', 'Release');
const outputFile = path.join(outputDir, 'embedded_mpv.node');
const outputLibDir = path.join(outputDir, 'lib');
const distNativeDir = path.join(
    workspaceRoot,
    'dist',
    'apps',
    'electron-backend',
    'native'
);
const unavailableMarkerFile = path.join(outputDir, 'embedded-mpv-unavailable.txt');
const homebrewIncludeDir = '/opt/homebrew/include';
const homebrewLibDir = '/opt/homebrew/lib';
const targetArch =
    process.env.IPTVNATOR_EMBEDDED_MPV_ARCH ||
    process.env.npm_config_arch ||
    process.arch;
const vendoredRuntimeRoot = path.join(
    workspaceRoot,
    'vendor',
    'embedded-mpv',
    `darwin-${targetArch}`
);
const vendoredIncludeDir = path.join(vendoredRuntimeRoot, 'include');
const vendoredLibDir = path.join(vendoredRuntimeRoot, 'lib');
const homebrewFallbackEnabled =
    process.env.IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW === '1';
const embeddedMpvRequired = ['1', 'true', 'yes', 'on'].includes(
    (process.env.IPTVNATOR_REQUIRE_EMBEDDED_MPV ?? '').trim().toLowerCase()
);

function log(message) {
    process.stdout.write(`[embedded-mpv] ${message}\n`);
}

function cleanOutput() {
    fs.rmSync(outputFile, { force: true });
    fs.rmSync(outputLibDir, { recursive: true, force: true });
    fs.rmSync(path.join(outputDir, '.deps'), { recursive: true, force: true });
    fs.rmSync(path.join(outputDir, 'obj.target'), {
        recursive: true,
        force: true,
    });
    fs.rmSync(path.join(outputDir, 'embedded-mpv-runtime.json'), {
        force: true,
    });
    fs.writeFileSync(
        unavailableMarkerFile,
        'Embedded MPV native runtime is not available for this build.\n'
    );
}

function cleanDistNativeOutput() {
    fs.rmSync(distNativeDir, { recursive: true, force: true });
}

function readRuntimeManifest(runtimeRoot) {
    const manifestPath = path.join(runtimeRoot, 'runtime-manifest.json');
    if (!fs.existsSync(manifestPath)) {
        return {};
    }

    return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
}

function resolveRuntime() {
    const vendoredLibMpv = findLibMpv(vendoredLibDir);
    const vendoredHeader = path.join(vendoredIncludeDir, 'mpv', 'client.h');

    if (vendoredLibMpv && fs.existsSync(vendoredHeader)) {
        return {
            origin: 'vendored-lgpl',
            includeDir: vendoredIncludeDir,
            libDir: vendoredLibDir,
            manifest: readRuntimeManifest(vendoredRuntimeRoot),
        };
    }

    if (homebrewFallbackEnabled) {
        const homebrewLibMpv = findLibMpv(homebrewLibDir);
        const homebrewHeader = path.join(homebrewIncludeDir, 'mpv', 'client.h');
        if (homebrewLibMpv && fs.existsSync(homebrewHeader)) {
            log(
                'Using Homebrew libmpv as a development-only fallback. Release packaging will reject this runtime.'
            );
            return {
                origin: 'homebrew-dev',
                includeDir: homebrewIncludeDir,
                libDir: homebrewLibDir,
                manifest: {
                    warning:
                        'Development-only runtime. Do not ship this in release artifacts.',
                },
            };
        }
    }

    return null;
}

function resolveElectronNodeGypBin() {
    const pnpmRoot = path.join(workspaceRoot, 'node_modules', '.pnpm');

    if (!fs.existsSync(pnpmRoot)) {
        throw new Error('Unable to find node_modules/.pnpm.');
    }

    const packageDirs = fs
        .readdirSync(pnpmRoot, { withFileTypes: true })
        .filter(
            (entry) =>
                entry.isDirectory() &&
                entry.name.startsWith('@electron+node-gyp@')
        )
        .map((entry) => entry.name)
        .sort();

    for (const packageDir of packageDirs) {
        const candidate = path.join(
            pnpmRoot,
            packageDir,
            'node_modules',
            '@electron',
            'node-gyp',
            'bin',
            'node-gyp.js'
        );

        if (fs.existsSync(candidate)) {
            return candidate;
        }
    }

    throw new Error('Unable to resolve @electron/node-gyp.');
}

function runNodeGyp(command, env) {
    const nodeGypBin = resolveElectronNodeGypBin();
    const result = spawnSync(
        process.execPath,
        [nodeGypBin, command, '--directory', addonRoot],
        {
            cwd: workspaceRoot,
            env,
            stdio: 'inherit',
        }
    );

    if (result.status !== 0) {
        throw new Error(`node-gyp ${command} failed with status ${result.status ?? 1}.`);
    }
}

function main() {
    fs.mkdirSync(outputDir, { recursive: true });
    cleanDistNativeOutput();

    if (process.platform !== 'darwin') {
        cleanOutput();
        if (embeddedMpvRequired) {
            throw new Error('Embedded MPV is required but this host is not macOS.');
        }
        log('Skipping build on non-macOS host.');
        return;
    }

    const runtime = resolveRuntime();
    if (!runtime) {
        cleanOutput();
        const message = [
            `Skipping build because no embedded MPV runtime was found for darwin-${targetArch}.`,
            `Expected vendored runtime at ${vendoredRuntimeRoot}.`,
            'For local development only, set IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1 to use Homebrew libmpv.',
        ].join('\n');
        if (embeddedMpvRequired) {
            throw new Error(message);
        }

        log(message);
        return;
    }

    const runtimeManifest = copyRuntimeToNativeBuild({
        runtimeLibDir: runtime.libDir,
        outputLibDir,
        runtimeOrigin: runtime.origin,
        runtimeManifest: {
            targetArch,
            ...runtime.manifest,
        },
    });
    fs.rmSync(unavailableMarkerFile, { force: true });

    const electronPackageJson = require(path.join(
        workspaceRoot,
        'node_modules',
        'electron',
        'package.json'
    ));
    const electronVersion = electronPackageJson.version;
    const env = {
        ...process.env,
        npm_config_runtime: 'electron',
        npm_config_target: electronVersion,
        npm_config_arch: targetArch,
        npm_config_disturl: 'https://electronjs.org/headers',
        npm_config_build_from_source: 'true',
        npm_config_update_binary: 'false',
        LIBMPV_INCLUDE_DIR: runtime.includeDir,
        LIBMPV_LIBRARY_DIR: outputLibDir,
    };

    log(
        `Building native addon against Electron ${electronVersion} using ${runtime.origin} runtime for darwin-${targetArch}...`
    );
    runNodeGyp('configure', env);
    runNodeGyp('build', env);

    if (!fs.existsSync(outputFile)) {
        throw new Error(`Build finished without producing ${outputFile}.`);
    }

    patchAddonForBundledRuntime(outputFile, outputLibDir);
    const forbiddenLinkErrors = validateNoForbiddenRuntimeLinks([
        outputFile,
        ...runtimeManifest.dylibs.map((dylib) => path.join(outputLibDir, dylib)),
    ]);
    if (runtime.origin === 'vendored-lgpl' && forbiddenLinkErrors.length > 0) {
        throw new Error(forbiddenLinkErrors.join('\n'));
    }

    log(`Built ${path.relative(workspaceRoot, outputFile)}.`);
}

try {
    main();
} catch (error) {
    process.stderr.write(
        `[embedded-mpv] ${error instanceof Error ? error.message : String(error)}\n`
    );
    process.exit(1);
}
