<?php

declare(strict_types=1);

/**
 * Shared CLI bootstrap and error policy for module runtime scripts.
 *
 * Policy:
 * - first-party warnings/notices/deprecations => fail-fast (RuntimeException)
 * - vendor warnings/notices/deprecations      => ignored and counted
 */

function moduleCliProjectRoot(): string
{
    return dirname(__DIR__);
}

function moduleCliVendorWarningsIgnoredTotal(): int
{
    $count = $GLOBALS['module_cli_vendor_warnings_ignored'] ?? 0;
    return is_int($count) ? $count : 0;
}

function moduleCliVendorWarningsIgnoredSince(int $before): int
{
    return max(0, moduleCliVendorWarningsIgnoredTotal() - $before);
}

function moduleCliIncrementVendorWarningsIgnored(): void
{
    $current = moduleCliVendorWarningsIgnoredTotal();
    $GLOBALS['module_cli_vendor_warnings_ignored'] = $current + 1;
}

function moduleCliSeverityName(int $severity): string
{
    return match ($severity) {
        E_WARNING => 'E_WARNING',
        E_NOTICE => 'E_NOTICE',
        E_USER_WARNING => 'E_USER_WARNING',
        E_USER_NOTICE => 'E_USER_NOTICE',
        E_DEPRECATED => 'E_DEPRECATED',
        E_USER_DEPRECATED => 'E_USER_DEPRECATED',
        default => 'E_' . $severity,
    };
}

function moduleCliInstallErrorPolicy(): void
{
    static $installed = false;
    if ($installed) {
        return;
    }
    $installed = true;

    error_reporting(E_ALL);
    ini_set('display_errors', '0');

    set_error_handler(static function (
        int $severity,
        string $message,
        string $file = '',
        int $line = 0
    ): bool {
        $handledSeverities = [
            E_WARNING,
            E_NOTICE,
            E_USER_WARNING,
            E_USER_NOTICE,
            E_DEPRECATED,
            E_USER_DEPRECATED,
        ];
        if (!in_array($severity, $handledSeverities, true)) {
            return false;
        }

        if ((error_reporting() & $severity) === 0) {
            return true;
        }

        $normalizedFile = str_replace('\\', '/', $file);
        if (str_contains($normalizedFile, '/vendor/')) {
            moduleCliIncrementVendorWarningsIgnored();
            return true;
        }

        throw new RuntimeException(sprintf(
            "First-party runtime warning (%s) in %s:%d: %s",
            moduleCliSeverityName($severity),
            $file,
            $line,
            $message
        ));
    });
}

function moduleCliBootstrapApp(): void
{
    static $booted = false;
    if ($booted) {
        return;
    }

    moduleCliInstallErrorPolicy();

    $projectRoot = moduleCliProjectRoot();
    chdir($projectRoot);

    require_once $projectRoot . '/vendor/autoload.php';
    require_once $projectRoot . '/config/config.php';
    require_once $projectRoot . '/lib/Support/helpers.php';
    $container = require $projectRoot . '/lib/App/registerContainer.php';
    setAppContainer($container);

    $booted = true;
}

/**
 * Runs one module CLI step with shared bootstrap, failure handling and vendor warning summary.
 *
 * @param callable():int $runner
 */
function moduleCliRunStep(string $stepName, callable $runner): int
{
    $warningsBefore = moduleCliVendorWarningsIgnoredTotal();
    moduleCliBootstrapApp();

    $exitCode = 0;
    try {
        $exitCode = $runner();
    } catch (Throwable $exception) {
        fwrite(STDERR, sprintf("%s: FAILED: %s\n", $stepName, $exception->getMessage()));
        $exitCode = 1;
    }

    $vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore);
    fwrite(STDOUT, sprintf("%s: vendor_warnings_ignored=%d\n", $stepName, $vendorWarningsIgnored));

    return $exitCode;
}

/**
 * Executes a callable under a non-blocking file lock.
 *
 * @param callable():int $runner
 */
function moduleCliWithFileLock(string $lockFile, string $busyMessage, callable $runner): int
{
    $lockDir = dirname($lockFile);
    if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) {
        throw new RuntimeException("Failed to create lock directory: {$lockDir}");
    }

    $lock = fopen($lockFile, 'c');
    if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
        fwrite(STDERR, $busyMessage . PHP_EOL);
        return 0;
    }

    try {
        return $runner();
    } finally {
        flock($lock, LOCK_UN);
        fclose($lock);
    }
}
