#pragma once

#include <string>
#include <functional>
#include <filesystem>

namespace fs = std::filesystem;

// Forward declare DownloadProgressCallback to avoid heavy model_manager.h include
namespace lemon {
    struct DownloadProgress;
    using DownloadProgressCallback = std::function<bool(const DownloadProgress&)>;
}

namespace lemon::backends {
    struct InstallParams {
        std::string repo;      // GitHub "org/repo"
        std::string filename;  // Release asset filename
    };

    struct BackendSpec {
        const std::string recipe;
        const std::string binary;

        using InstallParamsFn = InstallParams(*)(const std::string& backend, const std::string& version);
        InstallParamsFn install_params_fn;  // nullptr for backends with no auto-install

        BackendSpec(std::string r, std::string b, InstallParamsFn fn = nullptr)
            : recipe(std::move(r)), binary(std::move(b)), install_params_fn(fn) {}

        std::string log_name() const { return recipe + " Server"; };
    };

    // Return the backend spec for recipes that use the standard BackendSpec flow.
    // Returns nullptr for recipes that require custom handling (e.g., flm) or unknown recipes.
    const BackendSpec* try_get_spec_for_recipe(const std::string& recipe);

    /**
    * Utility functions for backend management
    */
    class BackendUtils {
    public:
        /**
        * Extract ZIP files (Windows/Linux built-in tools)
        * @param zip_path Path to the ZIP file
        * @param dest_dir Destination directory to extract to
        * @return true if extraction was successful, false otherwise
        */
        static bool extract_zip(const std::string& zip_path, const std::string& dest_dir, const std::string& backend_name);

        /**
        * Extract tar.gz files (Linux/macOS/Windows)
        * @param tarball_path Path to the tar.gz file
        * @param dest_dir Destination directory to extract to
        * @return true if extraction was successful, false otherwise
        */
        static bool extract_tarball(const std::string& tarball_path, const std::string& dest_dir, const std::string& backend_name);


        /**
        * Detect if archive is tar or zip
        * @param tarball_path Path to the archive file
        * @param dest_dir Destination directory to extract to
        * @return true if extraction was successful, false otherwise
        */
        static bool extract_archive(const std::string& archive_path, const std::string& dest_dir, const std::string& backend_name);

        /** Download and install the specified version of the backend from github.
         *  If progress_cb is provided, it receives download progress events instead of console output. */
        static void install_from_github(const BackendSpec& spec, const std::string& expected_version, const std::string& repo, const std::string& filename, const std::string& backend, DownloadProgressCallback progress_cb = nullptr);

        /** Get the latest version number for the given recipe/backend */
        static std::string get_backend_version(const std::string& recipe, const std::string& backend);

        /** Check if ROCm libraries are installed system-wide (Linux only) */
        static bool is_rocm_installed_system_wide();

        /** Get TheRock installation directory for a specific architecture and version */
        static std::string get_therock_install_dir(const std::string& arch, const std::string& version);

        /** Download and install TheRock ROCm tarball for the specified architecture (Linux only) */
        static void install_therock(const std::string& arch, const std::string& version,
                                   DownloadProgressCallback progress_cb = nullptr);

        /** Clean up old TheRock versions, keeping only the specified version */
        static void cleanup_old_therock_versions(const std::string& current_version);

        /** Get TheRock lib directory path if available, or empty string if not needed */
        static std::string get_therock_lib_path(const std::string& rocm_arch);

        /** Get the path to the backend's binary. Gives precedence to the path set through environment variables, if set. Throws if not found. */
        static std::string get_backend_binary_path(const BackendSpec& spec, const std::string& backend);

        /** Get the path where the version indicator is installed. Does not check existence. */
        static std::string get_installed_version_file(const BackendSpec& spec, const std::string& backend);

        /** Get the install directory for the backend. Generally only used internally by BackendUtils */
        static std::string get_install_directory(const std::string& dir_name, const std::string& backend);

        /** Find the executable in the installation directory. Generally only used internally by BackendUtils */
        static std::string find_executable_in_install_dir(const std::string& install_dir, const std::string& binary_name);

        /** Checks the environment for a variable following the scheme LEMONADE_BACKEND_VARIANT_BIN and return its value, if available. Generally only used internally by BackendUtils */
        static std::string find_external_backend_binary(const std::string& recipe, const std::string& backend);

        /**
         * Returns the raw user-supplied *_bin config value for this (recipe, backend),
         * e.g. "builtin" / "latest" / "b8664" / "/path/to/bin" / "". Empty string when
         * RuntimeConfig is unavailable or the key is unset. Does not validate or resolve.
         */
        static std::string get_bin_config_value(const std::string& recipe, const std::string& backend);

        /**
         * Build the (config_section, bin_key) pair used to look up a *_bin value
         * for a (recipe, backend). Handles the rocm channel collapse and the
         * "server_bin" singleton. Used by the lookup, install, and validation paths.
         */
        static void build_bin_config_key(const std::string& recipe,
                                         const std::string& backend,
                                         std::string& out_section,
                                         std::string& out_bin_key);
    };
} // namespace lemon::backends
