/*
 *  <Short Description>
 *  Copyright (C) 2025  Brett Terpstra
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include <iostream>
#include <blt/parse/argparse_v2.h>
#include <blt/std/assert.h>
#include <blt/meta/type_traits.h>
#include <blt/std/logging.h>
#include <blt/iterator/enumerate.h>
#include <blt/fs/path_helper.h>
#include <blt/std/string.h>

namespace blt::argparse
{
    constexpr static auto printer_primitive = [](const auto& v)
    {
        std::cout << v;
    };

    constexpr static auto printer_vector = [](const auto& v)
    {
        std::cout << "[";
        for (const auto& [i, a] : enumerate(v))
        {
            std::cout << a;
            if (i != v.size() - 1)
                std::cout << ", ";
        }
        std::cout << "]";
    };

    auto print_visitor = detail::arg_meta_type_helper_t::make_visitor(printer_primitive, printer_vector);

    template <typename T>
    size_t get_const_char_size(const T& t)
    {
        if constexpr (std::is_convertible_v<T, const char*>)
        {
            return std::char_traits<char>::length(t);
        }
        else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>)
        {
            return 1;
        }
        else if constexpr (std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string>)
        {
            return t.size();
        }
        else
        {
            return 0;
        }
    }

    template <typename T>
    auto ensure_is_string(T&& t)
    {
        if constexpr (std::is_arithmetic_v<meta::remove_cvref_t<T>> && !(std::is_same_v<T, char>
            || std::is_same_v<T, unsigned char> || std::is_same_v<T, signed char>))
            return std::to_string(std::forward<T>(t));
        else
            return std::forward<T>(t);
    }

    template <typename... Strings>
    std::string make_string(Strings&&... strings)
    {
        std::string out;
        out.reserve((get_const_char_size(strings) + ...));
        ((out += ensure_is_string(std::forward<Strings>(strings))), ...);
        return out;
    }

    template <typename... Strings>
    std::vector<std::string_view> make_arguments(Strings... strings)
    {
        return std::vector<std::string_view>{"./program", strings...};
    }

    template<typename T>
    void add(std::string& out, T&& value, size_t line_size = 60)
    {
        const auto lines = string::split(out, '\n');
        auto str = ensure_is_string(std::forward<T>(value));
        if (lines.empty())
        {
            out = str;
            return;
        }
        if (lines.back().size() + str.size() > line_size)
        {
            out += '\n';
            out += '\t';
        }
        out += str;
    }

    argument_builder_t& argument_builder_t::set_action(const action_t action)
    {
        m_action = action;
        switch (m_action)
        {
        case action_t::STORE_TRUE:
            set_nargs(0);
            as_type<bool>();
            set_default(false);
            break;
        case action_t::STORE_FALSE:
            set_nargs(0);
            as_type<bool>();
            set_default(true);
            break;
        case action_t::STORE_CONST:
        case action_t::APPEND_CONST:
            set_nargs(0);
            break;
        case action_t::COUNT:
            set_nargs(0);
            as_type<size_t>();
            break;
        case action_t::EXTEND:
            set_nargs(nargs_t::ALL);
            break;
        case action_t::HELP:
        case action_t::VERSION:
            set_nargs(0);
            break;
        default:
            break;
        }
        return *this;
    }

    argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest)
    {
        m_subparsers.emplace_back(dest, argument_subparser_t{*this});
        return m_subparsers.back().second;
    }

    argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer)
    {
        if (!m_name)
            m_name = fs::base_name_sv(consumer.absolute_first().get_argument());
        argument_positional_storage_t positional_storage{m_positional_arguments};
        hashset_t<std::string> found_flags;
        argument_storage_t parsed_args;
        // first, we consume flags which may be part of this parser
        while (consumer.can_consume() && consumer.peek().is_flag())
            handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume());

        for (auto& [key, subparser] : m_subparsers)
        {
            auto [parsed_subparser, storage] = subparser.parse(consumer);
            storage.m_data.emplace(std::string{key}, detail::arg_data_t{std::string{parsed_subparser.get_argument()}});
            parsed_args.add(storage);
        }

        while (consumer.can_consume())
        {
            if (consumer.peek().is_flag())
                handle_compound_flags(found_flags, parsed_args, consumer, consumer.consume());
            else
                parse_positional(parsed_args, consumer, positional_storage, consumer.peek().get_argument());
        }
        handle_missing_and_default_args(m_flag_arguments, found_flags, parsed_args, "flag");

        for (auto& [name, value] : positional_storage.remaining())
        {
            std::visit(lambda_visitor{
                           [](const nargs_t)
                           {
                           },
                           [](const int argc)
                           {
                               if (argc == 0)
                                   throw detail::bad_positional("Positional Argument takes no values, this is invalid!");
                           }
                       }, value.m_nargs);

            if (value.m_required)
                throw detail::missing_argument_error(make_string("Error: argument '", name, "' was not found but is required by the program"));
            if (value.m_default_value && !parsed_args.contains(value.m_dest.value_or(name)))
                parsed_args.m_data.emplace(value.m_dest.value_or(name), *value.m_default_value);
        }

        return parsed_args;
    }

    void argument_parser_t::print_help()
    {
        print_usage();
        std::cout << std::endl;
        std::string help;
        if (!m_flag_arguments.empty())
        {
            help += "Options:\n";
            hashmap_t<argument_builder_t*, std::vector<std::string>> same_flags;
            for (const auto& [key, value] : m_flag_arguments)
                same_flags[value].emplace_back(key);
            for (const auto& [builder, flag_list] : same_flags)
            {
                // find max size and algin?
                add(help, '\t');
                for (const auto& [i, flag] : enumerate(flag_list))
                {
                    add(help, flag);
                    if (i != flag_list.size() - 1)
                        add(help, ", ");
                }
            }
        }
    }

    void argument_parser_t::print_usage()
    {
        if (!m_usage)
        {
            std::string usage = m_name.value_or("");

            hashmap_t<std::string, std::vector<std::string>> singleFlags;
            std::vector<std::pair<argument_string_t, argument_builder_t*>> compoundFlags;

            for (const auto& [key, value] : m_flag_arguments)
            {
                argument_string_t arg{key, allowed_flag_prefixes};
                if (arg.get_flag().size() == 1)
                {
                    if (std::holds_alternative<i32>(value->m_nargs) && std::get<i32>(value->m_nargs) == 0)
                        singleFlags[arg.get_flag()].emplace_back(arg.get_name());
                    else
                        compoundFlags.emplace_back(arg, value);
                } else
                    compoundFlags.emplace_back(arg, value);
            }

            for (const auto& [i, kv] : enumerate(singleFlags))
            {
                const auto& [key, value] = kv;
                add(usage, "[");
                add(usage, key);
                for (const auto& name : value)
                    add(usage, name);
                add(usage, "]");
                if (i != singleFlags.size() - 1)
                    add(usage, " ");
            }

            for (const auto& [i, kv] : enumerate(compoundFlags))
            {
                const auto& [name, builder] = kv;
                add(usage, "[");
                add(usage, name.get_argument());
                auto lambda = [&]()
                {
                    add(usage, " ");
                    add(usage, builder->m_metavar.value_or(string::toUpperCase(name.get_name())));
                };
                std::visit(lambda_visitor{[&](const nargs_t)
                {
                    lambda();
                }, [&](const int argc)
                {
                    if (argc == 0)
                        return;
                    lambda();
                }}, builder->m_nargs);
                add(usage, "]");
                if (i != compoundFlags.size() - 1)
                    add(usage, " ");
            }

            for (const auto& [i, pair] : enumerate(m_positional_arguments))
            {
                const auto& [name, _] = pair;
                add(usage, "<");
                add(usage, name);
                add(usage, ">");
                if (i != m_positional_arguments.size() - 1)
                    add(usage, " ");
            }

            m_usage = usage;
        }
        std::cout << "Usage: " << *m_usage << std::endl;
    }

    void argument_parser_t::print_version() const
    {
        std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl;
    }

    void argument_parser_t::handle_compound_flags(hashset_t<std::string>& found_flags, argument_storage_t& parsed_args,
                                                  argument_consumer_t& consumer, const argument_string_t& arg)
    {
        // i kinda hate this, TODO?
        std::vector<std::string> compound_flags;
        if (arg.get_flag().size() == 1)
        {
            for (const auto c : arg.get_name())
                compound_flags.emplace_back(std::string{arg.get_flag()} + c);
        }
        else
        {
            if (arg.get_flag().size() > 2)
                throw detail::bad_flag(make_string("Error: Flag '", arg.get_argument(), "' is too long!"));
            compound_flags.emplace_back(arg.get_argument());
        }

        for (const auto& flag_key : compound_flags)
        {
            const auto flag = m_flag_arguments.find(flag_key);
            if (flag == m_flag_arguments.end())
                throw detail::bad_flag(make_string("Error: Unknown flag: ", flag_key));
            found_flags.insert(flag_key);
            parse_flag(parsed_args, consumer, flag_key);
        }
    }

    void argument_parser_t::parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, const std::string_view arg)
    {
        auto flag = m_flag_arguments.find(arg)->second;
        const auto dest = flag->m_dest.value_or(std::string{arg});
        std::visit(lambda_visitor{
                       [&parsed_args, &consumer, &dest, &flag, arg](const nargs_t arg_enum)
                       {
                           switch (arg_enum)
                           {
                           case nargs_t::IF_POSSIBLE:
                               if (consumer.can_consume() && !consumer.peek().is_flag())
                                   flag->m_dest_func(dest, parsed_args, consumer.consume().get_argument());
                               else
                               {
                                   if (flag->m_const_value)
                                       parsed_args.m_data.insert({dest, *flag->m_const_value});
                               }
                               break;
                           case nargs_t::ALL_AT_LEAST_ONE:
                               if (!consumer.can_consume())
                                   throw detail::missing_argument_error(
                                       make_string("Error expected at least one argument to be consumed by '", arg, '\''));
                               [[fallthrough]];
                           case nargs_t::ALL:
                               auto result = consume_until_flag_or_end(consumer, flag->m_choices ? &*flag->m_choices : nullptr);
                               if (!result)
                                   throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
                                                                              "' is not a valid choice for argument '", arg,
                                                                              "'! Expected one of ", result.error()));
                               flag->m_dest_vec_func(dest, parsed_args, result.value());
                               break;
                           }
                       },
                       [&parsed_args, &consumer, &dest, &flag, arg, this](const i32 argc)
                       {
                           const auto args = consume_argc(argc, consumer, flag->m_choices ? &*flag->m_choices : nullptr, arg);

                           switch (flag->m_action)
                           {
                           case action_t::STORE:
                               if (argc == 0)
                                   throw detail::missing_argument_error(
                                       make_string("Argument '", arg, "'s action is store but takes in no arguments?"));
                               if (argc == 1)
                                   flag->m_dest_func(dest, parsed_args, args.front());
                               else
                                   throw detail::unexpected_argument_error(make_string("Argument '", arg,
                                                                                       "'s action is store but takes in more than one argument. "
                                                                                       "Did you mean to use action_t::APPEND or action_t::EXTEND?"));
                               break;
                           case action_t::APPEND:
                           case action_t::EXTEND:
                               if (argc == 0)
                                   throw detail::missing_argument_error(
                                       make_string("Argument '", arg, "'s action is append or extend but takes in no arguments."));
                               flag->m_dest_vec_func(dest, parsed_args, args);
                               break;
                           case action_t::APPEND_CONST:
                               if (argc != 0)
                                   throw detail::unexpected_argument_error(
                                       make_string("Argument '", arg, "'s action is append const but takes in arguments."));
                               if (!flag->m_const_value)
                               {
                                   throw detail::missing_value_error(
                                       make_string("Append const chosen as an action but const value not provided for argument '", arg,
                                                   '\''));
                               }
                               if (parsed_args.contains(dest))
                               {
                                   auto& data = parsed_args.m_data[dest];
                                   std::visit(detail::arg_meta_type_helper_t::make_visitor(
                                                  [arg](auto& primitive)
                                                  {
                                                      throw detail::type_error(make_string(
                                                          "Invalid type for argument '", arg, "' expected list type, found '",
                                                          blt::type_string<decltype(primitive)>(), "' with value ", primitive));
                                                  },
                                                  [&flag, arg](auto& vec)
                                                  {
                                                      using type = typename meta::remove_cvref_t<decltype(vec)>::value_type;
                                                      if (!std::holds_alternative<type>(*flag->m_const_value))
                                                      {
                                                          throw detail::type_error(make_string("Constant value for argument '", arg,
                                                                                               "' type doesn't match values already present! Expected to be of type '",
                                                                                               blt::type_string<type>(), "'!"));
                                                      }
                                                      vec.push_back(std::get<type>(*flag->m_const_value));
                                                  }), data);
                               }
                               else
                               {
                                   std::visit(detail::arg_meta_type_helper_t::make_visitor(
                                                  [&parsed_args, &dest](auto& primitive)
                                                  {
                                                      std::vector<meta::remove_cvref_t<decltype(primitive)>> vec;
                                                      vec.emplace_back(primitive);
                                                      parsed_args.m_data.emplace(dest, std::move(vec));
                                                  },
                                                  [](auto&)
                                                  {
                                                      throw detail::type_error("Append const should not be a list type!");
                                                  }), *flag->m_const_value);
                               }
                               break;
                           case action_t::STORE_CONST:
                               if (argc != 0)
                               {
                                   print_usage();
                                   throw detail::unexpected_argument_error(
                                       make_string("Argument '", arg, "' is store const but called with an argument."));
                               }
                               if (!flag->m_const_value)
                                   throw detail::missing_value_error(
                                       make_string("Argument '", arg, "' is store const, but const storage has no value."));
                               parsed_args.m_data.emplace(dest, *flag->m_const_value);
                               break;
                           case action_t::STORE_TRUE:
                               if (argc != 0)
                               {
                                   print_usage();
                                   throw detail::unexpected_argument_error("Store true flag called with an argument.");
                               }
                               parsed_args.m_data.emplace(dest, true);
                               break;
                           case action_t::STORE_FALSE:
                               if (argc != 0)
                               {
                                   print_usage();
                                   throw detail::unexpected_argument_error("Store false flag called with an argument.");
                               }
                               parsed_args.m_data.insert({dest, false});
                               break;
                           case action_t::COUNT:
                               if (parsed_args.m_data.contains(dest))
                               {
                                   auto visitor = detail::arg_meta_type_helper_t::make_visitor(
                                       [](auto& primitive) -> detail::arg_data_t
                                       {
                                           using type = meta::remove_cvref_t<decltype(primitive)>;
                                           if constexpr (std::is_convertible_v<decltype(1), type>)
                                           {
                                               return primitive + static_cast<type>(1);
                                           }
                                           else
                                               throw detail::type_error(
                                                   "Error: count called but stored type is " + blt::type_string<type>());
                                       },
                                       [](auto&) -> detail::arg_data_t
                                       {
                                           throw detail::type_error("List present on count. This condition doesn't make any sense! "
                                               "(How did we get here, please report this!)");
                                       }
                                   );
                                   parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]);
                               }
                               else // I also hate this!
                                   flag->m_dest_func(dest, parsed_args, "1");
                               break;
                           case action_t::HELP:
                               print_help();
                               std::exit(0);
                           case action_t::VERSION:
                               print_version();
                               std::exit(0);
                           }
                       }
                   }, flag->m_nargs);
    }

    void argument_parser_t::parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage,
                                             const std::string_view arg)
    {
        if (!storage.has_positional())
            throw detail::missing_argument_error(make_string("Error: '", arg, "' positional argument does not match any defined for this parser"));
        auto& positional = storage.next();
        const auto dest = positional.m_dest.value_or(std::string{arg});
        std::visit(lambda_visitor{
                       [&consumer, &positional, &dest, &parsed_args, arg](const nargs_t arg_enum)
                       {
                           switch (arg_enum)
                           {
                           case nargs_t::IF_POSSIBLE:
                               throw detail::bad_positional(
                                   "Positional argument asked to consume if possible. We do not consider this to be a valid ask.");
                           case nargs_t::ALL_AT_LEAST_ONE:
                               if (!consumer.can_consume())
                                   throw detail::missing_argument_error(
                                       make_string("Error expected at least one argument to be consumed by '", arg, '\''));
                               [[fallthrough]];
                           case nargs_t::ALL:
                               auto result = consume_until_flag_or_end(
                                   consumer, positional.m_choices ? &*positional.m_choices : nullptr);
                               if (!result)
                                   throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
                                                                              "' is not a valid choice for argument '", arg,
                                                                              "'! Expected one of ", result.error()));
                               positional.m_dest_vec_func(dest, parsed_args, result.value());
                               break;
                           }
                       },
                       [this, &consumer, &positional, &dest, &parsed_args, arg](const i32 argc)
                       {
                           const auto args = consume_argc(argc, consumer, positional.m_choices ? &*positional.m_choices : nullptr, arg);

                           switch (positional.m_action)
                           {
                           case action_t::STORE:
                               if (argc == 0)
                                   throw detail::missing_argument_error(
                                       make_string("Argument '", arg, "'s action is store but takes in no arguments?"));
                               if (argc == 1)
                                   positional.m_dest_func(dest, parsed_args, args.front());
                               else
                                   throw detail::unexpected_argument_error(make_string("Argument '", arg,
                                                                                       "'s action is store but takes in more than one argument. "
                                                                                       "Did you mean to use action_t::APPEND or action_t::EXTEND?"));
                               break;
                           case action_t::APPEND:
                           case action_t::EXTEND:
                               if (argc == 0)
                                   throw detail::missing_argument_error(
                                       make_string("Argument '", arg, "'s action is append or extend but takes in no arguments."));
                               positional.m_dest_vec_func(dest, parsed_args, args);
                               break;
                           case action_t::APPEND_CONST:
                               throw detail::bad_positional("action_t::APPEND_CONST does not make sense for positional arguments");
                           case action_t::STORE_CONST:
                               throw detail::bad_positional("action_t::STORE_CONST does not make sense for positional arguments");
                           case action_t::STORE_TRUE:
                               throw detail::bad_positional("action_t::STORE_TRUE does not make sense for positional arguments");
                           case action_t::STORE_FALSE:
                               throw detail::bad_positional("action_t::STORE_FALSE does not make sense for positional arguments");
                           case action_t::COUNT:
                               throw detail::bad_positional("action_t::COUNT does not make sense for positional arguments");
                           case action_t::HELP:
                               print_help();
                               std::exit(0);
                           case action_t::VERSION:
                               print_version();
                               std::exit(0);
                           }
                       }
                   }, positional.m_nargs);
    }

    void argument_parser_t::handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
                                                            const hashset_t<std::string>& found, argument_storage_t& parsed_args,
                                                            const std::string_view type)
    {
        for (const auto& [key, value] : arguments)
        {
            if (!found.contains(key))
            {
                if (value->m_required)
                    throw detail::missing_argument_error(make_string("Error: ", type, " argument '", key,
                                                                     "' was not found but is required by the program"));
                auto dest = value->m_dest.value_or(std::string{key});
                if (value->m_default_value && !parsed_args.contains(dest))
                    parsed_args.m_data.emplace(dest, *value->m_default_value);
            }
        }
    }

    expected<std::vector<std::string>, std::string> argument_parser_t::consume_until_flag_or_end(argument_consumer_t& consumer,
                                                                                                 hashset_t<std::string>* allowed_choices)
    {
        std::vector<std::string> args;
        while (consumer.can_consume() && !consumer.peek().is_flag())
        {
            if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
            {
                std::string valid_choices = "{";
                for (const auto& [i, choice] : enumerate(*allowed_choices))
                {
                    valid_choices += choice;
                    if (i != allowed_choices->size() - 1)
                        valid_choices += ", ";
                }
                valid_choices += "}";
                return unexpected(valid_choices);
            }
            args.emplace_back(consumer.consume().get_argument());
        }
        return args;
    }

    std::vector<std::string> argument_parser_t::consume_argc(const int argc, argument_consumer_t& consumer,
                                                             hashset_t<std::string>* allowed_choices,
                                                             const std::string_view arg)
    {
        std::vector<std::string> args;
        for (i32 i = 0; i < argc; ++i)
        {
            if (!consumer.can_consume())
            {
                throw detail::missing_argument_error(
                    make_string("Expected ", argc, " arguments to be consumed by '", arg, "' but found ", i));
            }
            if (consumer.peek().is_flag())
            {
                std::cout << "Warning: arg '" << arg << "' expects " << argc <<
                    " arguments to be consumed but we found a flag '" << consumer.peek().
                                                                                  get_argument() <<
                    "'. We will comply as this may be desired if this argument is a file." << std::endl;
            }
            if (allowed_choices != nullptr && !allowed_choices->contains(consumer.peek().get_argument()))
            {
                std::string valid_choices = "{";
                for (const auto& [i, choice] : enumerate(*allowed_choices))
                {
                    valid_choices += choice;
                    if (i != allowed_choices->size() - 1)
                        valid_choices += ", ";
                }
                valid_choices += "}";
                throw detail::bad_choice_error(make_string('\'', consumer.peek().get_argument(),
                                                           "' is not a valid choice for argument '", arg,
                                                           "'! Expected one of ", valid_choices));
            }
            args.emplace_back(consumer.consume().get_argument());
        }
        if (args.size() != static_cast<size_t>(argc))
        {
            throw std::runtime_error(
                "This error condition should not be possible. "
                "Args consumed didn't equal the arguments requested and previous checks didn't fail. "
                "Please report as an issue on the GitHub");
        }
        return args;
    }

    std::pair<argument_string_t, argument_storage_t> argument_subparser_t::parse(argument_consumer_t& consumer)
    {
        if (!consumer.can_consume())
            throw detail::missing_argument_error("Subparser requires an argument.");
        const auto key = consumer.consume();
        if (key.is_flag())
            throw detail::subparse_error(key.get_argument(), get_allowed_strings());
        const auto it = m_parsers.find(key.get_name());
        argument_parser_t* parser;
        if (it == m_parsers.end())
        {
            const auto it2 = m_aliases.find(key.get_name());
            if (it2 == m_aliases.end())
                throw detail::subparse_error(key.get_argument(), get_allowed_strings());
            parser = it2->second;
        }
        else
            parser = &it->second;
        parser->m_name = m_parent->m_name;
        return {key, parser->parse(consumer)};
    }

    std::vector<std::vector<std::string_view>> argument_subparser_t::get_allowed_strings() const
    {
        std::vector<std::vector<std::string_view>> vec;
        for (const auto& [key, value] : m_parsers)
        {
            std::vector<std::string_view> aliases;
            aliases.push_back(key);
            for (const auto& [alias, parser] : m_aliases)
            {
                if (parser == &value)
                    aliases.push_back(alias);
            }
            vec.emplace_back(std::move(aliases));
        }
        return vec;
    }

    namespace detail
    {
        // Unit Tests for class argument_string_t
        // Test Case 1: Ensure the constructor handles flags correctly
        void test_argument_string_t_flag_basic(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("-f", prefixes);
            BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
            BLT_ASSERT(arg.value() == "f" && "Flag value should match the input string.");
        }

        // Test Case 2: Ensure the constructor handles long flags correctly
        void test_argument_string_t_long_flag(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("--file", prefixes);
            BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
            BLT_ASSERT(arg.value() == "file" && "Long flag value should match the input string.");
        }

        // Test Case 3: Ensure positional arguments are correctly identified
        void test_argument_string_t_positional_argument(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("filename.txt", prefixes);
            BLT_ASSERT(!arg.is_flag() && "Expected argument to be identified as positional.");
            BLT_ASSERT(arg.value() == "filename.txt" && "Positional argument value should match the input string.");
        }

        // Test Case 5: Handle an empty string
        void test_argument_string_t_empty_input(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("", prefixes);
            BLT_ASSERT(!arg.is_flag() && "Expected an empty input to be treated as positional, not a flag.");
            BLT_ASSERT(arg.value().empty() && "Empty input should have an empty value.");
        }

        // Test Case 6: Handle edge case of a single hyphen (`-`) which might be ambiguous
        void test_argument_string_t_single_hyphen(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("-", prefixes);
            BLT_ASSERT(arg.is_flag() && "Expected single hyphen (`-`) to be treated as a flag.");
            BLT_ASSERT(arg.value().empty() && "Single hyphen flag should have empty value.");
            BLT_ASSERT(arg.get_flag() == "-" && "Single hyphen flag should match the input string.");
        }

        // Test Case 8: Handle arguments with prefix only (like "--")
        void test_argument_string_t_double_hyphen(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("--", prefixes);
            BLT_ASSERT(arg.is_flag() && "Double hyphen ('--') should be treated as a flag.");
            BLT_ASSERT(arg.value().empty() && "Double hyphen flag should have empty value.");
            BLT_ASSERT(arg.get_flag() == "--" && "Double hyphen value should match the input string.");
        }

        // Test Case 9: Validate edge case of an argument with spaces
        void test_argument_string_t_with_spaces(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("  ", prefixes);
            BLT_ASSERT(!arg.is_flag() && "Arguments with spaces should not be treated as flags.");
            BLT_ASSERT(arg.value() == "  " && "Arguments with spaces should match the input string.");
        }

        // Test Case 10: Validate arguments with numeric characters
        void test_argument_string_t_numeric_flag(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("-123", prefixes);
            BLT_ASSERT(arg.is_flag() && "Numeric flags should still be treated as flags.");
            BLT_ASSERT(arg.value() == "123" && "Numeric flag value should match the input string.");
        }

        // Test Case 11: Ensure the constructor handles '+' flag correctly
        void test_argument_string_t_plus_flag_basic(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("+f", prefixes);
            BLT_ASSERT(arg.is_flag() && "Expected argument to be identified as a flag.");
            BLT_ASSERT(arg.value() == "f" && "Plus flag value should match the input string.");
        }

        // Test Case 13: Handle edge case of a single plus (`+`) which might be ambiguous
        void test_argument_string_t_single_plus(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("+", prefixes);
            BLT_ASSERT(arg.is_flag() && "Expected single plus (`+`) to be treated as a flag.");
            BLT_ASSERT(arg.value().empty() && "Single plus flag should have empty value.");
            BLT_ASSERT(arg.get_flag() == "+" && "Single plus flag should match the input string.");
        }

        // Test Case 14: Handle arguments with prefix only (like '++')
        void test_argument_string_t_double_plus(const hashset_t<char>& prefixes)
        {
            const argument_string_t arg("++", prefixes);
            BLT_ASSERT(arg.is_flag() && "Double plus ('++') should be treated as a flag.");
            BLT_ASSERT(arg.value().empty() && "Double plus flag should have empty value.");
            BLT_ASSERT(arg.get_flag() == "++" && "Double plus value should match the input string.");
        }


        void run_all_tests_argument_string_t()
        {
            const hashset_t<char> prefixes = {'-', '+'};
            test_argument_string_t_flag_basic(prefixes);
            test_argument_string_t_long_flag(prefixes);
            test_argument_string_t_positional_argument(prefixes);
            test_argument_string_t_empty_input(prefixes);
            test_argument_string_t_single_hyphen(prefixes);
            test_argument_string_t_double_hyphen(prefixes);
            test_argument_string_t_with_spaces(prefixes);
            test_argument_string_t_numeric_flag(prefixes);
            test_argument_string_t_plus_flag_basic(prefixes);
            test_argument_string_t_single_plus(prefixes);
            test_argument_string_t_double_plus(prefixes);
        }

        void test_argparse_empty()
        {
            const std::vector<std::string> argv{"./program"};
            argument_parser_t parser;
            const auto args = parser.parse(argv);
            BLT_ASSERT(args.size() == 0 && "Empty argparse should have no args on output");
        }

        void test_single_flag_prefixes()
        {
            argument_parser_t parser;
            parser.add_flag("-a").set_action(action_t::STORE_TRUE);
            parser.add_flag("+b").set_action(action_t::STORE_FALSE);
            parser.add_flag("/c").as_type<int>().set_action(action_t::STORE);

            const std::vector<std::string> args = {"./program", "-a", "+b", "/c", "42"};
            const auto parsed_args = parser.parse(args);

            BLT_ASSERT(parsed_args.get<bool>("-a") == true && "Flag '-a' should store `true`");
            BLT_ASSERT(parsed_args.get<bool>("+b") == false && "Flag '+b' should store `false`");
            BLT_ASSERT(parsed_args.get<int>("/c") == 42 && "Flag '/c' should store the value 42");
        }

        // Test: Invalid flag prefixes
        void test_invalid_flag_prefixes()
        {
            argument_parser_t parser;
            parser.add_flag("-a");
            parser.add_flag("+b");
            parser.add_flag("/c");

            const std::vector<std::string> args = {"./program", "!d", "-a"};
            try
            {
                parser.parse(args);
                BLT_ASSERT(false && "Parsing should fail with invalid flag prefix '!'");
            }
            catch (...)
            {
                BLT_ASSERT(true && "Correctly threw on bad flag prefix");
            }
        }

        void test_compound_flags()
        {
            argument_parser_t parser;
            parser.add_flag("-v").as_type<int>().set_action(action_t::COUNT);

            const std::vector<std::string> args = {"./program", "-vvv"};
            const auto parsed_args = parser.parse(args);

            BLT_ASSERT(parsed_args.get<size_t>("-v") == 3 && "Flag '-v' should count occurrences in compound form");
        }

        void test_combination_of_valid_and_invalid_flags()
        {
            using namespace argparse;

            argument_parser_t parser;
            parser.add_flag("-x").as_type<int>();
            parser.add_flag("/y").as_type<std::string>();

            const std::vector<std::string> args = {"./program", "-x", "10", "!z", "/y", "value"};
            try
            {
                parser.parse(args);
                BLT_ASSERT(false && "Parsing should fail due to invalid flag '!z'");
            }
            catch (...)
            {
                BLT_ASSERT(true && "Correctly threw an exception for invalid flag");
            }
        }

        void test_flags_with_different_actions()
        {
            using namespace argparse;

            argument_parser_t parser;
            parser.add_flag("-k").as_type<int>().set_action(action_t::STORE); // STORE action
            parser.add_flag("-t").as_type<int>().set_action(action_t::STORE_CONST).set_const(999); // STORE_CONST action
            parser.add_flag("-f").set_action(action_t::STORE_FALSE); // STORE_FALSE action
            parser.add_flag("-c").set_action(action_t::STORE_TRUE); // STORE_TRUE action

            const std::vector<std::string> args = {"./program", "-k", "100", "-t", "-f", "-c"};
            const auto parsed_args = parser.parse(args);

            BLT_ASSERT(parsed_args.get<int>("-k") == 100 && "Flag '-k' should store 100");
            BLT_ASSERT(parsed_args.get<int>("-t") == 999 && "Flag '-t' should store a const value of 999");
            BLT_ASSERT(parsed_args.get<bool>("-f") == false && "Flag '-f' should store `false`");
            BLT_ASSERT(parsed_args.get<bool>("-c") == true && "Flag '-c' should store `true`");
        }

        // Helper function to simulate argument parsing for nargs tests
        bool parse_arguments(const std::vector<std::string_view>& args, const nargs_v expected_nargs)
        {
            argument_parser_t parser;

            std::vector<argument_string_t> arg_strings;
            arg_strings.reserve(args.size());
            for (const auto& arg : args)
                arg_strings.emplace_back(arg, parser.get_allowed_flag_prefixes());
            argument_consumer_t consumer{arg_strings};

            parser.add_positional("positional").set_nargs(expected_nargs);
            try
            {
                auto parsed_args = parser.parse(consumer);
                return consumer.remaining() == 0;
            }
            catch (const std::exception&)
            {
                return false;
            }
        }

        // Test case for nargs = 0
        void test_nargs_0()
        {
            std::cout << "[Running Test: test_nargs_0]\n";

            // Valid case: No arguments
            const std::vector<std::string_view> valid_args = {"./program"};
            BLT_ASSERT(!parse_arguments(valid_args, 0) && "nargs=0: Should fail");

            // Invalid case: 1 argument
            const std::vector<std::string_view> invalid_args = {"./program", "arg1"};
            BLT_ASSERT(!parse_arguments(invalid_args, 0) && "nargs=0: Should not accept any arguments");

            std::cout << "Success: test_nargs_0\n";
        }

        // Test case for nargs = 1
        void test_nargs_1()
        {
            std::cout << "[Running Test: test_nargs_1]\n";

            // Valid case: 1 argument
            const std::vector<std::string_view> valid_args = {"./program", "arg1"};
            BLT_ASSERT(parse_arguments(valid_args, 1) && "nargs=1: Should accept exactly 1 argument");

            // Invalid case: 0 arguments
            const std::vector<std::string_view> invalid_args_0 = {"./program"};
            BLT_ASSERT(!parse_arguments(invalid_args_0, 1) && "nargs=1: Should not accept 0 arguments");

            // Invalid case: 2 arguments
            const std::vector<std::string_view> invalid_args_2 = {"./program", "arg1", "arg2"};
            BLT_ASSERT(!parse_arguments(invalid_args_2, 1) && "nargs=1: Should not accept more than 1 argument");

            std::cout << "Success: test_nargs_1\n";
        }

        // Test case for nargs = 2
        void test_nargs_2()
        {
            std::cout << "[Running Test: test_nargs_2]\n";

            // Valid case: 2 arguments
            const std::vector<std::string_view> valid_args = {"./program", "arg1", "arg2"};
            BLT_ASSERT(!parse_arguments(valid_args, 2) && "nargs=2: Should fail as action is store");

            // Invalid case: 0 arguments
            const std::vector<std::string_view> invalid_args_0 = {"./program"};
            BLT_ASSERT(!parse_arguments(invalid_args_0, 2) && "nargs=2: Should not accept 0 arguments");

            // Invalid case: 1 argument
            const std::vector<std::string_view> invalid_args_1 = {"./program", "arg1"};
            BLT_ASSERT(!parse_arguments(invalid_args_1, 2) && "nargs=2: Should not accept less than 2 arguments");

            // Invalid case: 3 arguments
            const std::vector<std::string_view> invalid_args_3 = {"./program", "arg1", "arg2", "arg3"};
            BLT_ASSERT(!parse_arguments(invalid_args_3, 2) && "nargs=2: Should not accept more than 2 arguments");

            std::cout << "Success: test_nargs_2\n";
        }

        void test_nargs_all()
        {
            std::cout << "[Running Test: test_nargs_all]\n";

            // Valid case: No arguments
            const std::vector<std::string_view> valid_args_0 = {"./program"};
            BLT_ASSERT(!parse_arguments(valid_args_0, argparse::nargs_t::ALL) &&
                "nargs=ALL: No arguments present. Should fail.)");

            // Valid case: Multiple arguments
            const std::vector<std::string_view> valid_args_2 = {"./program", "arg1", "arg2"};
            BLT_ASSERT(parse_arguments(valid_args_2, argparse::nargs_t::ALL) &&
                "nargs=ALL: Should accept all remaining arguments");

            // Valid case: Many arguments
            const std::vector<std::string_view> valid_args_many = {"./program", "arg1", "arg2", "arg3", "arg4"};
            BLT_ASSERT(parse_arguments(valid_args_many, argparse::nargs_t::ALL) &&
                "nargs=ALL: Should accept all remaining arguments");

            std::cout << "Success: test_nargs_all\n";
        }

        // Test case for nargs_t::ALL_AT_LEAST_ONE
        void test_nargs_all_at_least_one()
        {
            std::cout << "[Running Test: test_nargs_all_at_least_one]\n";

            // Valid case: 1 argument
            const std::vector<std::string_view> valid_args_1 = {"./program", "arg1"};
            BLT_ASSERT(parse_arguments(valid_args_1, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
                "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume it");

            // Valid case: Multiple arguments
            const std::vector<std::string_view> valid_args_3 = {"./program", "arg1", "arg2", "arg3"};
            BLT_ASSERT(parse_arguments(valid_args_3, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
                "nargs=ALL_AT_LEAST_ONE: Should accept at least one argument and consume all remaining arguments");

            // Invalid case: No arguments
            const std::vector<std::string_view> invalid_args_0 = {"./program"};
            BLT_ASSERT(!parse_arguments(invalid_args_0, argparse::nargs_t::ALL_AT_LEAST_ONE) &&
                "nargs=ALL_AT_LEAST_ONE: Should reject if no arguments are provided");

            std::cout << "Success: test_nargs_all_at_least_one\n";
        }

        void run_combined_flag_test()
        {
            std::cout << "[Running Test: run_combined_flag_test]\n";
            argument_parser_t parser;

            parser.add_flag("-a").set_action(action_t::STORE_TRUE);
            parser.add_flag("--deep").set_action(action_t::STORE_FALSE);
            parser.add_flag("-b", "--combined").set_action(action_t::STORE_CONST).set_const(50);
            parser.add_flag("--append").set_action(action_t::APPEND).as_type<int>();
            parser.add_flag("--required").set_required(true);
            parser.add_flag("--default").set_default("I am a default value");
            parser.add_flag("-t").set_action(action_t::APPEND_CONST).set_dest("test").set_const(5);
            parser.add_flag("-g").set_action(action_t::APPEND_CONST).set_dest("test").set_const(10);
            parser.add_flag("-e").set_action(action_t::APPEND_CONST).set_dest("test").set_const(15);
            parser.add_flag("-f").set_action(action_t::APPEND_CONST).set_dest("test").set_const(20);
            parser.add_flag("-d").set_action(action_t::APPEND_CONST).set_dest("test").set_const(25);
            parser.add_flag("--end").set_action(action_t::EXTEND).set_dest("wow").as_type<float>();

            const auto a1 = make_arguments("-a", "--required", "hello");
            const auto r1 = parser.parse(a1);
            BLT_ASSERT(r1.get<bool>("-a") == true && "Flag '-a' should store true");
            BLT_ASSERT(r1.get<std::string>("--default") == "I am a default value" && "Flag '--default' should store default value");
            BLT_ASSERT(r1.get("--required") == "hello" && "Flag '--required' should store 'hello'");

            const auto a2 = make_arguments("-a", "--deep", "--required", "soft");
            const auto r2 = parser.parse(a2);
            BLT_ASSERT(r2.get<bool>("-a") == true && "Flag '-a' should store true");
            BLT_ASSERT(r2.get<bool>("--deep") == false && "Flag '--deep' should store false");
            BLT_ASSERT(r2.get("--required") == "soft" && "Flag '--required' should store 'soft'");

            const auto a3 = make_arguments("--required", "silly", "--combined", "-t", "-f", "-e");
            const auto r3 = parser.parse(a3);
            BLT_ASSERT((r3.get<std::vector<int>>("test") == std::vector{5, 20, 15}) && "Flags should add to vector of {5, 20, 15}");
            BLT_ASSERT(r3.get<int>("-b") == 50 && "Combined flag should store const of 50");

            const auto a4 = make_arguments("--required", "crazy", "--end", "10", "12.05", "68.11", "100.00", "200532", "-d", "-t", "-g", "-e", "-f");
            const auto r4 = parser.parse(a4);
            BLT_ASSERT(
                (r4.get<std::vector<int>>("test") == std::vector{25, 5, 10, 15, 20}) &&
                "Expected test vector to be filled with all arguments in order of flags");
            BLT_ASSERT(
                (r4.get<std::vector<float>>("wow") == std::vector<float>{10, 12.05, 68.11, 100.00, 200532}) &&
                "Extend vector expected to contain all elements");

            std::cout << "Success: run_combined_flag_test\n";
        }

        void run_subparser_test()
        {
            std::cout << "[Running Test: run_subparser_test]\n";
            argument_parser_t parser;

            parser.add_flag("--open").set_flag();

            auto& subparser = parser.add_subparser("mode");

            auto& n1 = subparser.add_parser("n1");
            n1.add_flag("--silly").set_flag();
            n1.add_positional("path");

            auto& n2 = subparser.add_parser("n2");
            n2.add_flag("--crazy").set_flag();
            n2.add_positional("path");
            n2.add_positional("output");

            auto& n3 = subparser.add_parser("n3");
            n3.add_flag("--deep").set_flag();

            const auto a1 = make_arguments("n1", "--silly");
            try
            {
                parser.parse(a1);
                BLT_ASSERT(false && "Subparser should throw an error when positional not supplied");
            }
            catch (...)
            {
            }

            const auto a2 = make_arguments("--open");
            try
            {
                parser.parse(a2);
                BLT_ASSERT(false && "Subparser should throw an error when no subparser is supplied");
            }
            catch (...)
            {
            }

            const auto a3 = make_arguments("n1", "--silly", "path_n1");
            const auto r3 = parser.parse(a3);
            BLT_ASSERT(r3.get<bool>("--open") == false && "Flag '--open' should default to false");
            BLT_ASSERT(r3.get("mode") == "n1" && "Subparser should store 'n1'");
            BLT_ASSERT(r3.get("path") == "path_n1" && "Subparser path should be 'path'");

            const auto a4 = make_arguments("n2", "--crazy", "path");

            try
            {
                parser.parse(a4);
                BLT_ASSERT(false && "Subparser should throw an error when second positional is not supplied");
            }
            catch (...)
            {
            }

            std::cout << std::endl;

            const auto a5 = make_arguments("--open", "n2", "path_n2", "output_n2");
            const auto r5 = parser.parse(a5);
            BLT_ASSERT(r5.get<bool>("--open") == true && "Flag '--open' should store true");
            BLT_ASSERT(r5.get("mode") == "n2" && "Subparser should store 'n2'");
            BLT_ASSERT(r5.get("path") == "path_n2" && "Subparser path should be 'path'");
            BLT_ASSERT(r5.get("output") == "output_n2" && "Subparser output should be 'output'");

            const auto a6 = make_arguments("not_an_option", "silly");

            try
            {
                parser.parse(a6);
                BLT_ASSERT(false && "Subparser should throw an error when first positional is not a valid subparser");
            } catch (const std::exception&)
            {
            }

            const auto a7 = make_arguments("n3");
            const auto r7 = parser.parse(a7);
            BLT_ASSERT(r7.get("mode") == "n3" && "Subparser should store 'n3'");

            std::cout << "Success: run_subparser_test\n";
        }

        void run_argparse_flag_tests()
        {
            test_single_flag_prefixes();
            test_invalid_flag_prefixes();
            test_compound_flags();
            test_combination_of_valid_and_invalid_flags();
            test_flags_with_different_actions();
            run_combined_flag_test();
            run_subparser_test();
        }

        void run_all_nargs_tests()
        {
            test_nargs_0();
            test_nargs_1();
            test_nargs_2();
            test_nargs_all();
            test_nargs_all_at_least_one();
            std::cout << "All nargs tests passed successfully.\n";
        }

        void test()
        {
            run_all_tests_argument_string_t();
            test_argparse_empty();
            run_argparse_flag_tests();
            run_all_nargs_tests();
        }

        [[nodiscard]] std::string subparse_error::error_string() const
        {
            std::string message = "Subparser Error: ";
            message += m_found_string;
            message += " is not a valid command. Allowed commands are: {";
            for (const auto [i, allowed_string] : enumerate(m_allowed_strings))
            {
                if (allowed_string.size() > 1)
                    message += '[';
                for (const auto [j, alias] : enumerate(allowed_string))
                {
                    message += alias;
                    if (static_cast<i64>(j) < static_cast<i64>(allowed_string.size()) - 2)
                        message += ", ";
                    else if (static_cast<i64>(j) < static_cast<i64>(allowed_string.size()) - 1)
                        message += ", or ";
                }
                if (allowed_string.size() > 1)
                    message += ']';
                if (i != m_allowed_strings.size() - 1)
                    message += ", ";
            }
            message += "}";
            return message;
        }
    }
}