// Copyright (c) 2022, QuantStack and Mamba Contributors
//
// Distributed under the terms of the BSD 3-Clause License.
//
// The full license is in the file LICENSE, distributed with this software.


#ifndef MAMBA_CORE_ENVIRONMENT_LOCKFILE_HPP
#define MAMBA_CORE_ENVIRONMENT_LOCKFILE_HPP

#include <string>
#include <typeindex>
#include <optional>
#include <unordered_map>

#include <tl/expected.hpp>

#include "fsutil.hpp"
#include "package_info.hpp"
#include "error_handling.hpp"

namespace mamba
{

    enum class file_parsing_error_code
    {
        unknown_failure,     /// Something failed while parsing but we can't identify what.
        unsuported_version,  /// The version of the file does not matched supported ver.
        parsing_failure,     /// The content of the file doesnt match the expected format/language
                             /// structure or constraints.
        invalid_data,        /// The structure of the data in the file is fine but some fields have
                             /// invalid values for our purpose.
    };

    struct EnvLockFileError
    {
        file_parsing_error_code parsing_error_code = file_parsing_error_code::unknown_failure;
        std::optional<std::type_index> yaml_error_type{};

        static const EnvLockFileError& get_details(const mamba_error& error)
        {
            return std::any_cast<const EnvLockFileError&>(error.data());
        }

        template <typename StringT>
        static mamba_error make_error(file_parsing_error_code error_code,
                                      StringT&& msg,
                                      std::optional<std::type_index> yaml_error_type = std::nullopt)
        {
            return mamba_error{ std::forward<StringT>(msg),
                                mamba_error_code::env_lockfile_parsing_failed,
                                EnvLockFileError{ error_code, yaml_error_type } };
        }
    };

    class EnvironmentLockFile
    {
    public:
        struct Channel
        {
            std::string url;
            std::vector<std::string> used_env_vars;
        };

        struct Meta
        {
            std::unordered_map<std::string, std::string> content_hash;
            std::vector<Channel> channels;
            std::vector<std::string> platforms;
            std::vector<std::string> sources;
        };

        struct Package
        {
            mamba::PackageInfo info;
            bool is_optional = false;
            std::string category;
            std::string manager;
            std::string platform;
        };

        EnvironmentLockFile(Meta metadata, std::vector<Package> packages)
            : metadata(std::move(metadata))
            , packages(std::move(packages))
        {
        }

        std::vector<PackageInfo> get_packages_for(std::string_view category,
                                                  std::string_view platform,
                                                  std::string_view manager) const;

        const std::vector<Package>& get_all_packages() const
        {
            return packages;
        }
        const Meta& get_metadata() const
        {
            return metadata;
        }

    private:
        Meta metadata;
        std::vector<Package> packages;
    };

    /// Read an environment lock YAML file and returns it's structured content or an error if
    /// failed.
    tl::expected<EnvironmentLockFile, mamba_error> read_environment_lockfile(
        const fs::u8path& lockfile_location);


    /// Returns `true` if the filename matches names of files which should be interpreted as conda
    /// environment lockfile. NOTE: this does not check if the file exists.
    inline bool is_env_lockfile_name(const std::string_view filename)
    {
        return ends_with(filename, "-lock.yml") || ends_with(filename, "-lock.yaml");
    }


}


#endif
