<?php

namespace Illuminate\Foundation\Console;

use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Env;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

use function Termwind\terminal;

#[AsCommand(name: 'serve')]
class ServeCommand extends Command
{
    protected $name = 'serve';
    protected static $defaultName = 'serve';
    protected $description = 'Serve the application on the PHP development server';

    protected $portOffset = 0;
    protected $serverRunningHasBeenDisplayed = false;
    protected $envCache = [];

    public static $passthroughVariables = [
        'APP_ENV', 'LARAVEL_SAIL', 'PATH', 'PHP_CLI_SERVER_WORKERS',
        'PHP_IDE_CONFIG', 'SYSTEMROOT', 'XDEBUG_CONFIG', 'XDEBUG_MODE', 'XDEBUG_SESSION',
    ];

    public function handle()
    {
        $environmentFile = $this->option('env')
            ? base_path('.env') . '.' . $this->option('env')
            : base_path('.env');

        $hasEnvironment = file_exists($environmentFile);
        $lastModified = $hasEnvironment ? filemtime($environmentFile) : now()->addDays(30)->getTimestamp();

        $this->cacheEnvVars($hasEnvironment);

        while ($this->portOffset < $this->input->getOption('tries')) {
            $process = $this->startProcess();

            while ($process->isRunning()) {
                if (! $this->option('no-reload') && $hasEnvironment && filemtime($environmentFile) > $lastModified) {
                    $lastModified = filemtime($environmentFile);
                    $this->components->info('Environment modified. Restarting server...');
                    $process->stop(5);
                    $this->serverRunningHasBeenDisplayed = false;
                    $process = $this->startProcess();
                }
                usleep(200_000); // Faster sleep to support 20 instances
            }

            $status = $process->getExitCode();
            if ($status === 0) return 0;
            $this->portOffset++;
        }

        return 1; // No available ports
    }

    protected function startProcess()
    {
        $process = new Process(
            $this->serverCommand(),
            public_path(),
            $this->envCache
        );

        $process->start($this->handleProcessOutput());

        return $process;
    }

    protected function serverCommand()
    {
        $serverFile = file_exists(base_path('server.php'))
            ? base_path('server.php')
            : __DIR__ . '/../resources/server.php';

        return [
            (new PhpExecutableFinder)->find(false),
            '-S',
            $this->host() . ':' . $this->port(),
            $serverFile,
        ];
    }

    protected function host()
    {
        [$host] = $this->getHostAndPort();
        return $host;
    }

    protected function port()
    {
        $port = $this->input->getOption('port');
        if (is_null($port)) [, $port] = $this->getHostAndPort();
        return ($port ?: 8000) + $this->portOffset;
    }

    protected function getHostAndPort()
    {
        $parts = explode(':', $this->input->getOption('host'));
        return [$parts[0], $parts[1] ?? null];
    }

    protected function cacheEnvVars($hasEnvironment)
    {
        $this->envCache = collect($_ENV)->mapWithKeys(function ($value, $key) use ($hasEnvironment) {
            if ($this->option('no-reload') || ! $hasEnvironment) return [$key => $value];
            return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false];
        })->all();
    }

    protected function handleProcessOutput()
    {
        return fn($type, $buffer) => str($buffer)->explode("\n")->each(function ($line) {
            if (str($line)->contains('Development Server (http')) {
                if (! $this->serverRunningHasBeenDisplayed) {
                    $this->components->info("Server running on [http://{$this->host()}:{$this->port()}].");
                    $this->comment('  <fg=yellow;options=bold>Press Ctrl+C to stop the server</>');
                    $this->newLine();
                    $this->serverRunningHasBeenDisplayed = true;
                }
            } elseif (! empty($line)) {
                $warning = explode('] ', $line);
                $this->components->warn(count($warning) > 1 ? $warning[1] : $warning[0]);
            }
        });
    }

    protected function getOptions()
    {
        return [
            ['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on', Env::get('SERVER_HOST', '127.0.0.1')],
            ['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on', Env::get('SERVER_PORT')],
            ['tries', null, InputOption::VALUE_OPTIONAL, 'The max number of ports to attempt to serve from', 20],
            ['no-reload', null, InputOption::VALUE_NONE, 'Do not reload the development server on .env file changes'],
        ];
    }
}
