#include "presentation/localization/LocaleRepository.h"

#include "presentation/localization/LocaleMetadata.h"

#include <algorithm>
#include <cctype>
#include <fstream>
#include <sstream>
#include <system_error>

#include "foundation/serialization/JsonFlatParser.h"
#include "foundation/diagnostics/Logger.h"
#include "foundation/text/StringUtils.h"

namespace ta::presentation::localization
{
    namespace
    {

        std::vector<std::string> split_aliases(const std::string& value)
        {
            std::vector<std::string> aliases;
            std::size_t start = 0;
            while (start <= value.size())
            {
                const std::size_t end = value.find(',', start);
                std::string alias = value.substr(start, end == std::string::npos ? end : end - start);
                alias.erase(alias.begin(), std::find_if(alias.begin(), alias.end(), [](const unsigned char c)
                {
                    return std::isspace(c) == 0;
                }));
                alias.erase(std::find_if(alias.rbegin(), alias.rend(), [](const unsigned char c)
                {
                    return std::isspace(c) == 0;
                }).base(), alias.end());
                if (!alias.empty())
                    aliases.push_back(ta::foundation::text::lower_copy(std::move(alias)));
                if (end == std::string::npos)
                    break;
                start = end + 1;
            }
            return aliases;
        }
    }

    LocaleRepository::LocaleRepository(std::filesystem::path directory)
        : directory_(std::move(directory))
    {
    }

    std::filesystem::path LocaleRepository::safe_file_path(const std::string_view file_name) const
    {
        const std::filesystem::path relative(file_name);
        if (relative.empty() || relative.is_absolute() || relative.has_parent_path())
            return {};
        return directory_ / relative;
    }

    TranslationMap LocaleRepository::load(const std::string_view file_name) const
    {
        TranslationMap result;
        const std::filesystem::path path = safe_file_path(file_name);
        if (path.empty())
        {
            ta::foundation::logger::error("RESOURCE", "unsafe locale file name rejected | file=" + std::string(file_name));
            return result;
        }

        std::ifstream input(path, std::ios::binary);
        if (!input.good())
        {
            ta::foundation::logger::warn("RESOURCE", "locale file unavailable | path=" + path.string());
            return result;
        }

        std::ostringstream buffer;
        buffer << input.rdbuf();
        if (input.bad() || !ta::foundation::serialization::json::parse_flat_object(buffer.str(), result))
        {
            ta::foundation::logger::error("RESOURCE", "localization parse failed | file=" + path.filename().string());
            result.clear();
        }
        return result;
    }

    std::vector<LocaleOption> LocaleRepository::discover() const
    {
        std::vector<LocaleOption> options;
        std::error_code ec;
        for (const auto& entry : std::filesystem::directory_iterator(directory_, ec))
        {
            if (ec)
                break;
            if (!entry.is_regular_file(ec) || ec || ta::foundation::text::lower_copy(entry.path().extension().string()) != ".json")
            {
                ec.clear();
                continue;
            }

            const std::string file_name = entry.path().filename().string();
            TranslationMap metadata = load(file_name);
            const auto id = metadata.find(locale_metadata::locale_id);
            const auto name = metadata.find(locale_metadata::locale_name);
            if (id == metadata.end() || name == metadata.end() || id->second.empty() || name->second.empty())
            {
                ta::foundation::logger::warn("RESOURCE", "localization metadata missing | file=" + file_name);
                continue;
            }

            LocaleOption option{};
            option.id = id->second;
            option.label = name->second;
            option.file_name = file_name;
            if (const auto aliases = metadata.find(locale_metadata::locale_aliases); aliases != metadata.end())
                option.system_aliases = split_aliases(aliases->second);
            option.system_aliases.push_back(ta::foundation::text::lower_copy(option.id));

            const bool duplicate = std::any_of(options.begin(), options.end(), [&](const LocaleOption& existing)
            {
                return existing.id == option.id;
            });
            if (!duplicate)
                options.push_back(std::move(option));
        }

        std::sort(options.begin(), options.end(), [](const LocaleOption& left, const LocaleOption& right)
        {
            if (left.id == "en") return true;
            if (right.id == "en") return false;
            return ta::foundation::text::lower_copy(left.label) < ta::foundation::text::lower_copy(right.label);
        });

        if (options.empty())
            options.push_back({"en", "English", "en.json", {"en"}});
        return options;
    }

}
