<?php

declare(strict_types=1);

namespace Fennec\Config;

/**
 * Parseur de fichiers de configuration au format CLE=valeur (ex. .env.local).
 *
 * Gère les commentaires pleine ligne (#), les valeurs entre guillemets (jamais castées),
 * et le cast automatique des valeurs non citées vers bool/int/float.
 */
final class EnvFileParser
{
    /**
     * @param string $filePath Chemin vers le fichier de configuration.
     *
     * @return array<string, string|int|float|bool> Variables castées, indexées par clé.
     *
     * @throws ConfigFileNotFoundException Si le fichier n'existe pas ou n'est pas lisible.
     * @throws ConfigParseException Si une ligne ne respecte pas le format attendu (CLE=valeur).
     */
    public function parseFile(string $filePath): array
    {
        if (!\is_file($filePath) || !\is_readable($filePath)) {
            throw new ConfigFileNotFoundException($filePath);
        }

        return $this->parseString(\file_get_contents($filePath));
    }

    /**
     * @param string $content Contenu brut d'un fichier de configuration.
     *
     * @return array<string, string|int|float|bool> Variables castées, indexées par clé.
     *
     * @throws ConfigParseException Si une ligne ne respecte pas le format attendu (CLE=valeur).
     */
    public function parseString(string $content): array
    {
        $values = [];

        foreach (\preg_split('/\r\n|\r|\n/', $content) as $line) {
            $trimmedLine = \trim($line);

            if ($trimmedLine === '' || $trimmedLine[0] === '#') {
                continue;
            }

            if (!\str_contains($trimmedLine, '=')) {
                throw new ConfigParseException($line);
            }

            [$key, $rawValue] = \explode('=', $trimmedLine, 2);
            $key = \trim($key);
            $rawValue = \trim($rawValue);

            $values[$key] = $this->isQuoted($rawValue)
                ? \substr($rawValue, 1, -1)
                : $this->castValue($rawValue);
        }

        return $values;
    }

    /**
     * Indique si une valeur brute est entourée d'une paire de guillemets identiques.
     */
    private function isQuoted(string $rawValue): bool
    {
        return \strlen($rawValue) >= 2
            && (
                ($rawValue[0] === '"' && \str_ends_with($rawValue, '"'))
                || ($rawValue[0] === "'" && \str_ends_with($rawValue, "'"))
            );
    }

    /**
     * Caste une valeur non citée vers bool/int/float, ou la laisse en string.
     */
    private function castValue(string $rawValue): string|int|float|bool
    {
        return match (true) {
            \strcasecmp($rawValue, 'true') === 0 => true,
            \strcasecmp($rawValue, 'false') === 0 => false,
            \is_numeric($rawValue) && !\str_contains($rawValue, '.') => (int) $rawValue,
            \is_numeric($rawValue) => (float) $rawValue,
            default => $rawValue,
        };
    }
}
