<?php

declare(strict_types=1);

namespace MintyPHP\Console\Support;

use RuntimeException;
use Throwable;

final class ModuleCliRuntime
{
    private static bool $booted = false;
    private static bool $errorPolicyInstalled = false;
    private static int $vendorWarningsIgnored = 0;

    private function __construct()
    {
    }

    public static function bootstrap(): void
    {
        if (self::$booted) {
            return;
        }

        self::installErrorPolicy();

        $projectRoot = CliAppBootstrap::projectRoot();
        chdir($projectRoot);

        require_once $projectRoot . '/vendor/autoload.php';
        require_once $projectRoot . '/config/config.php';
        require_once $projectRoot . '/core/Support/helpers.php';

        $container = require $projectRoot . '/core/App/registerContainer.php';
        setAppContainer($container);

        self::$booted = true;
    }

    public static function projectRoot(): string
    {
        return CliAppBootstrap::projectRoot();
    }

    public static function vendorWarningsIgnoredTotal(): int
    {
        return self::$vendorWarningsIgnored;
    }

    /**
     * @param callable():int $runner
     * @return array{step: string, status: string, exit_code: int, vendor_warnings_ignored: int, error: string|null}
     */
    public static function runStep(string $stepName, callable $runner): array
    {
        $warningsBefore = self::vendorWarningsIgnoredTotal();
        self::bootstrap();

        $exitCode = 0;
        $error = null;
        try {
            $exitCode = $runner();
        } catch (Throwable $throwable) {
            $exitCode = 1;
            $error = $throwable->getMessage();
        }

        return [
            'step' => $stepName,
            'status' => $exitCode === 0 ? 'ok' : 'failed',
            'exit_code' => $exitCode,
            'vendor_warnings_ignored' => max(0, self::vendorWarningsIgnoredTotal() - $warningsBefore),
            'error' => $error,
        ];
    }

    /**
     * @param callable():int $runner
     * @return array{busy: bool, message: string|null, exit_code: int}
     */
    public static function withFileLock(string $lockFile, string $busyMessage, callable $runner): array
    {
        $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)) {
            if (is_resource($lock)) {
                fclose($lock);
            }
            return [
                'busy' => true,
                'message' => $busyMessage,
                'exit_code' => 0,
            ];
        }

        try {
            return [
                'busy' => false,
                'message' => null,
                'exit_code' => $runner(),
            ];
        } finally {
            flock($lock, LOCK_UN);
            fclose($lock);
        }
    }

    private static function installErrorPolicy(): void
    {
        if (self::$errorPolicyInstalled) {
            return;
        }
        self::$errorPolicyInstalled = 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/')) {
                self::$vendorWarningsIgnored++;
                return true;
            }

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

    private static function severityName(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,
        };
    }
}
