<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator;

use Butschster\ContextGenerator\Application\FSPath;
use Spiral\Core\Attribute\Singleton;

/**
 * This class manages application paths using FSPath for path manipulation.
 * It's immutable and provides methods to create new instances with modified paths.
 * It's a central component for path storage within the application.
 */
#[Singleton]
final readonly class Directories implements DirectoriesInterface
{
    private FSPath $binaryPathObj;
    private FSPath $rootPathObj;
    private FSPath $outputPathObj;
    private FSPath $configPathObj;
    private ?FSPath $envFilePathObj;

    /**
     * Create a new Directories instance with the specified paths.
     *
     * @param string $rootPath The root path of the project, used as base for resolving relative paths
     * @param string $outputPath The path where compiled documents will be saved
     * @param string $configPath The path where configuration files are located
     * @param string $jsonSchemaPath The path to the JSON schema file
     * @param string|null $envFilePath Optional path to an environment file
     */
    public function __construct(
        public string $rootPath,
        public string $outputPath,
        public string $configPath,
        public string $jsonSchemaPath,
        public ?string $envFilePath = null,
        public ?string $binaryPath = null,
    ) {
        // Ensure paths are not empty
        if ($rootPath === '') {
            throw new \InvalidArgumentException('Root path cannot be empty');
        }
        if ($outputPath === '') {
            throw new \InvalidArgumentException('Output path cannot be empty');
        }
        if ($configPath === '') {
            throw new \InvalidArgumentException('Config path cannot be empty');
        }
        if ($jsonSchemaPath === '') {
            throw new \InvalidArgumentException('JSON schema path cannot be empty');
        }

        // Initialize FSPath objects
        $this->binaryPathObj = FSPath::create($this->binaryPath ?? $this->resolveBinaryPath());
        $this->rootPathObj = FSPath::create($rootPath);
        $this->outputPathObj = FSPath::create($outputPath);
        $this->configPathObj = FSPath::create($configPath);
        $this->envFilePathObj = $envFilePath !== null ? FSPath::create($envFilePath) : null;
    }

    public function getBinaryPath(): FSPath
    {
        return $this->binaryPathObj;
    }

    public function getRootPath(): FSPath
    {
        return $this->rootPathObj;
    }

    public function getOutputPath(): FSPath
    {
        return $this->outputPathObj;
    }

    public function getConfigPath(): FSPath
    {
        return $this->configPathObj;
    }

    public function getJsonSchemaPath(): string
    {
        return $this->jsonSchemaPath;
    }

    public function getEnvFilePath(): ?FSPath
    {
        return $this->envFilePathObj;
    }

    public function withRootPath(?string $rootPath): self
    {
        if ($rootPath === null) {
            return $this;
        }

        return new self(
            rootPath: $rootPath,
            outputPath: $this->outputPath,
            configPath: $this->configPath,
            jsonSchemaPath: $this->jsonSchemaPath,
            envFilePath: $this->envFilePath,
            binaryPath: $this->binaryPath,
        );
    }

    public function withConfigPath(?string $configPath): self
    {
        if ($configPath === null) {
            return $this;
        }

        return new self(
            rootPath: $this->rootPath,
            outputPath: $this->outputPath,
            configPath: $configPath,
            jsonSchemaPath: $this->jsonSchemaPath,
            envFilePath: $this->envFilePath,
            binaryPath: $this->binaryPath,
        );
    }

    public function withOutputPath(?string $outputPath): self
    {
        if ($outputPath === null) {
            return $this;
        }

        return new self(
            rootPath: $this->rootPath,
            outputPath: $outputPath,
            configPath: $this->configPath,
            jsonSchemaPath: $this->jsonSchemaPath,
            envFilePath: $this->envFilePath,
            binaryPath: $this->binaryPath,
        );
    }

    public function withEnvFile(?string $envFileName): self
    {
        if ($envFileName === null) {
            return $this;
        }

        $envFilePath = $this->rootPathObj->join($envFileName)->toString();

        return new self(
            rootPath: $this->rootPath,
            outputPath: $this->outputPath,
            configPath: $this->configPath,
            jsonSchemaPath: $this->jsonSchemaPath,
            envFilePath: $envFilePath,
            binaryPath: $this->binaryPath,
        );
    }

    public function determineRootPath(?string $configPath = null, ?string $inlineConfig = null): self
    {
        if ($configPath === null || $inlineConfig !== null) {
            return $this;
        }

        // If relative, resolve against the original root path
        $configPathObj = FSPath::create($configPath);

        if ($configPathObj->isRelative()) {
            $configPath = $this->rootPathObj->join($configPath)->toString();
        }

        // If config path is absolute, use its directory as root
        $configDir = \rtrim(\is_dir($configPath) ? $configPath : \dirname($configPath), '/');

        if (\is_dir($configDir)) {
            return $this->withRootPath($configDir)->withConfigPath($configPath);
        }

        return $this;
    }

    private function resolveBinaryPath(): string
    {
        // Primary: Use argv[0] (most reliable for CLI)
        if (isset($_SERVER['argv'][0])) {
            return $_SERVER['argv'][0];
        }

        // Fallback 1: PHP_SELF
        if (isset($_SERVER['PHP_SELF'])) {
            return $_SERVER['PHP_SELF'];
        }

        // Fallback 2: SCRIPT_NAME
        if (isset($_SERVER['SCRIPT_NAME'])) {
            return $_SERVER['SCRIPT_NAME'];
        }

        throw new \RuntimeException('Unable to determine binary path');
    }
}
