<?php

declare(strict_types=1);

namespace StaticPHP\Config;

use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Registry\Registry;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;
use Symfony\Component\Yaml\Yaml;

class PackageConfig
{
    private static array $package_configs = [];

    /**
     * Load package configurations from a specified directory.
     * It will look for files matching the pattern 'pkg.*.json' and 'pkg.json'.
     */
    public static function loadFromDir(string $dir, string $registry_name): array
    {
        if (!is_dir($dir)) {
            throw new WrongUsageException("Directory {$dir} does not exist, cannot load pkg.json config.");
        }
        $dir = rtrim($dir, '/');
        $loaded = [];
        $files = FileSystem::scanDirFiles($dir, false);
        if (is_array($files)) {
            foreach ($files as $file) {
                self::loadFromFile($file, $registry_name);
                $loaded[] = $file;
            }
        }
        if (file_exists("{$dir}/pkg.json")) {
            self::loadFromFile("{$dir}/pkg.json", $registry_name);
            $loaded[] = "{$dir}/pkg.json";
        }
        return $loaded;
    }

    /**
     * Load package configurations from a specified JSON file.
     *
     * @param string $file the path to the json package configuration file
     */
    public static function loadFromFile(string $file, string $registry_name): string
    {
        $content = file_get_contents($file);
        if ($content === false) {
            throw new WrongUsageException("Failed to read package config file: {$file}");
        }
        // judge extension — use cache to skip redundant parsing
        $data = ConfigCache::get($content);
        if ($data !== null) {
            logger()->debug("Config cache hit: {$file}");
        } else {
            $data = match (pathinfo($file, PATHINFO_EXTENSION)) {
                'json' => json_decode($content, true),
                'yml', 'yaml' => extension_loaded('yaml') ? yaml_parse($content) : Yaml::parse($content),
                default => throw new WrongUsageException("Unsupported package config file format: {$file}"),
            };
            if (is_array($data)) {
                ConfigCache::set($content, $data);
            }
        }
        ConfigValidator::validateAndLintPackages(basename($file), $data);
        foreach ($data as $pkg_name => $config) {
            self::$package_configs[$pkg_name] = $config;
            Registry::_bindPackageConfigFile($pkg_name, $registry_name, $file);

            // Register inline artifact if present
            if (isset($config['artifact']) && is_array($config['artifact'])) {
                ArtifactConfig::registerInlineArtifact(
                    $pkg_name,
                    $config['artifact'],
                    $registry_name,
                    "inline in {$file}"
                );
            }
        }
        return $file;
    }

    public static function loadFromArray(array $data, string $registry_name): void
    {
        ConfigValidator::validateAndLintPackages('array_input', $data);
        foreach ($data as $pkg_name => $config) {
            self::$package_configs[$pkg_name] = $config;
            Registry::_bindPackageConfigFile($pkg_name, $registry_name, 'array_input');

            // Register inline artifact if present
            if (isset($config['artifact']) && is_array($config['artifact'])) {
                ArtifactConfig::registerInlineArtifact(
                    $pkg_name,
                    $config['artifact'],
                    $registry_name,
                    'inline in array_input'
                );
            }
        }
    }

    /**
     * Check if a package configuration exists.
     */
    public static function isPackageExists(string $pkg_name): bool
    {
        return isset(self::$package_configs[$pkg_name]);
    }

    public static function getAll(): array
    {
        return self::$package_configs;
    }

    /**
     * Get a specific field from a package configuration.
     *
     * @param  string      $pkg_name   Package name
     * @param  null|string $field_name Package config field name
     * @param  null|mixed  $default    Default value if field not found
     * @return mixed       The value of the specified field or the default value
     */
    public static function get(string $pkg_name, ?string $field_name = null, mixed $default = null): mixed
    {
        if (!self::isPackageExists($pkg_name)) {
            return $default;
        }
        // use suffixes to find field
        $suffixes = match (SystemTarget::getTargetOS()) {
            'Windows' => ['@windows', ''],
            'Darwin' => ['@macos', '@unix', ''],
            'Linux' => ['@linux', '@unix', ''],
            'BSD' => ['@freebsd', '@bsd', '@unix', ''],
        };
        if ($field_name === null) {
            return self::$package_configs[$pkg_name];
        }
        if (in_array($field_name, ConfigValidator::SUFFIX_ALLOWED_FIELDS)) {
            foreach ($suffixes as $suffix) {
                $suffixed_field = $field_name . $suffix;
                if (isset(self::$package_configs[$pkg_name][$suffixed_field])) {
                    return self::$package_configs[$pkg_name][$suffixed_field];
                }
            }
            return $default;
        }
        return self::$package_configs[$pkg_name][$field_name] ?? $default;
    }
}
