@ 261106.4
current
/* auto-generated on 2026-07-27 16:10:09 -0400. Do not edit! */
/* begin file include/ada.h */
/**
 * @file ada.h
 * @brief Main header for the Ada URL parser library.
 *
 * This is the primary entry point for the Ada URL parser library. Including
 * this single header provides access to the complete Ada API, including:
 *
 * - URL parsing via `ada::parse()` function
 * - Two URL representations: `ada::url` and `ada::url_aggregator`
 * - URL search parameters via `ada::url_search_params`
 * - URL pattern matching via `ada::url_pattern` (URLPattern API)
 * - IDNA (Internationalized Domain Names) support
 *
 * @example
 * ```cpp
 *
 * // Parse a URL
 * auto url = ada::parse("https://example.com/path?query=1");
 * if (url) {
 *     std::cout << url->get_hostname(); // "example.com"
 * }
 * ```
 *
 * @see https://url.spec.whatwg.org/ - WHATWG URL Standard
 * @see https://github.com/ada-url/ada - Ada URL Parser GitHub Repository
 */
#ifndef ADA_H
#define ADA_H

/* begin file include/ada/ada_idna.h */
/* auto-generated on 2026-07-12 20:34:08 -0400. Do not edit! */
/* begin file include/idna.h */
#ifndef ADA_IDNA_H
#define ADA_IDNA_H

/* begin file include/ada/idna/unicode_transcoding.h */
#ifndef ADA_IDNA_UNICODE_TRANSCODING_H
#define ADA_IDNA_UNICODE_TRANSCODING_H

#include <string>
#include <string_view>

namespace ada::idna {

size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output);

size_t utf8_length_from_utf32(const char32_t* buf, size_t len);

size_t utf32_length_from_utf8(const char* buf, size_t len);

size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output);

}  // namespace ada::idna

#endif  // ADA_IDNA_UNICODE_TRANSCODING_H
/* end file include/ada/idna/unicode_transcoding.h */
/* begin file include/ada/idna/mapping.h */
#ifndef ADA_IDNA_MAPPING_H
#define ADA_IDNA_MAPPING_H

#include <string>
#include <string_view>

namespace ada::idna {

// If the input is ascii, then the mapping is just -> lower case.
void ascii_map(char* input, size_t length);
// Map the characters according to IDNA, returning the empty string on error.
std::u32string map(std::u32string_view input);
// Map into an existing buffer (cleared on entry). Returns false if any code
// point is disallowed. Reusing the buffer avoids repeated heap allocations
// when called in a loop over multiple labels.
bool map(std::u32string_view input, std::u32string& out);

}  // namespace ada::idna

#endif
/* end file include/ada/idna/mapping.h */
/* begin file include/ada/idna/normalization.h */
#ifndef ADA_IDNA_NORMALIZATION_H
#define ADA_IDNA_NORMALIZATION_H

#include <string>
#include <string_view>

namespace ada::idna {

// Returns true if `input` is already in Unicode Normalization Form C.
// Requires that internal tables have been loaded (call ensure via normalize
// or map first, or this returns false if tables are unavailable).
[[nodiscard]] bool is_already_nfc(std::u32string_view input) noexcept;

// Normalize the characters according to IDNA (Unicode Normalization Form C).
// Returns false if the internal Unicode tables could not be loaded; in that
// case `input` is left unchanged. Skips work when the string is already NFC.
[[nodiscard]] bool normalize(std::u32string& input);

}  // namespace ada::idna
#endif
/* end file include/ada/idna/normalization.h */
/* begin file include/ada/idna/punycode.h */
#ifndef ADA_IDNA_PUNYCODE_H
#define ADA_IDNA_PUNYCODE_H

#include <string>
#include <string_view>

namespace ada::idna {

bool punycode_to_utf32(std::string_view input, std::u32string& out);
bool verify_punycode(std::string_view input);
bool utf32_to_punycode(std::u32string_view input, std::string& out);

}  // namespace ada::idna

#endif  // ADA_IDNA_PUNYCODE_H
/* end file include/ada/idna/punycode.h */
/* begin file include/ada/idna/validity.h */
#ifndef ADA_IDNA_VALIDITY_H
#define ADA_IDNA_VALIDITY_H

#include <string>
#include <string_view>

namespace ada::idna {

/**
 * @see https://www.unicode.org/reports/tr46/#Validity_Criteria
 */
bool is_label_valid(std::u32string_view label);

}  // namespace ada::idna

#endif  // ADA_IDNA_VALIDITY_H
/* end file include/ada/idna/validity.h */
/* begin file include/ada/idna/to_ascii.h */
#ifndef ADA_IDNA_TO_ASCII_H
#define ADA_IDNA_TO_ASCII_H

#include <string>
#include <string_view>

/* begin file include/ada/idna/limits.h */
#ifndef ADA_IDNA_LIMITS_H
#define ADA_IDNA_LIMITS_H

#include <cstddef>

namespace ada::idna {

// Maximum accepted UTF-8 domain length for to_ascii / to_unicode.
// Bounds heap growth under untrusted input (DoS resistance). DNS wire limits
// are smaller; this allows long Unicode labels used in URL tests/fixtures.
inline constexpr size_t max_domain_input_bytes = 16384;

}  // namespace ada::idna

#endif  // ADA_IDNA_LIMITS_H
/* end file include/ada/idna/limits.h */

namespace ada::idna {

// Converts a domain (e.g., www.google.com) possibly containing international
// characters to an ascii domain (with punycode). It will not do percent
// decoding: percent decoding should be done prior to calling this function. We
// do not remove tabs and spaces, they should have been removed prior to calling
// this function. We also do not trim control characters. We also assume that
// the input is not empty. We return "" on error. Inputs longer than
// max_domain_input_bytes are rejected.
//
// This function may accept or even produce invalid domains (WHATWG carve-outs).
std::string to_ascii(std::string_view ut8_string);

// Same as to_ascii, but writes into `out` and returns false on error without
// relying on empty-string ambiguity.
[[nodiscard]] bool to_ascii(std::string_view ut8_string, std::string& out);

// Returns true if the string contains a forbidden code point according to the
// WHATGL URL specification:
// https://url.spec.whatwg.org/#forbidden-domain-code-point
bool contains_forbidden_domain_code_point(std::string_view ascii_string);

bool constexpr is_ascii(std::u32string_view view);
bool constexpr is_ascii(std::string_view view);

}  // namespace ada::idna

#endif  // ADA_IDNA_TO_ASCII_H
/* end file include/ada/idna/to_ascii.h */
/* begin file include/ada/idna/to_unicode.h */
#ifndef ADA_IDNA_TO_UNICODE_H
#define ADA_IDNA_TO_UNICODE_H

#include <string>
#include <string_view>

namespace ada::idna {

// UTS #46 ToUnicode. Never fails per the standard: on step failure the original
// label is kept. Inputs longer than max_domain_input_bytes are returned
// unchanged as a safety measure under untrusted input.
std::string to_unicode(std::string_view input);

// Writes into `out`. Returns false only if the input exceeds
// max_domain_input_bytes (out is left empty). Otherwise always returns true
// (ToUnicode does not fail).
[[nodiscard]] bool to_unicode(std::string_view input, std::string& out);

}  // namespace ada::idna

#endif  // ADA_IDNA_TO_UNICODE_H
/* end file include/ada/idna/to_unicode.h */
/* begin file include/ada/idna/identifier.h */
#ifndef ADA_IDNA_IDENTIFIER_H
#define ADA_IDNA_IDENTIFIER_H

#include <string>
#include <string_view>

namespace ada::idna {

// Verify if it is valid name code point given a Unicode code point and a
// boolean first: If first is true return the result of checking if code point
// is contained in the IdentifierStart set of code points. Otherwise return the
// result of checking if code point is contained in the IdentifierPart set of
// code points. Returns false if the input is empty or the code point is not
// valid. There is minimal Unicode error handling: the input should be valid
// UTF-8. https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point
bool valid_name_code_point(char32_t code_point, bool first);

}  // namespace ada::idna

#endif
/* end file include/ada/idna/identifier.h */

#endif
/* end file include/idna.h */
/* end file include/ada/ada_idna.h */
/* begin file include/ada/character_sets.h */
/**
 * @file character_sets.h
 * @brief Declaration of the character sets used by unicode functions.
 * @author Node.js
 * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc
 */
#ifndef ADA_CHARACTER_SETS_H
#define ADA_CHARACTER_SETS_H

/* begin file include/ada/common_defs.h */
/**
 * @file common_defs.h
 * @brief Cross-platform compiler macros and common definitions.
 *
 * This header provides compiler-specific macros for optimization hints,
 * platform detection, SIMD support detection, and development/debug utilities.
 * It ensures consistent behavior across different compilers (GCC, Clang, MSVC).
 */
#ifndef ADA_COMMON_DEFS_H
#define ADA_COMMON_DEFS_H

// https://en.cppreference.com/w/cpp/feature_test#Library_features
// detect C++20 features
#include <version>

#ifdef _MSC_VER
#define ADA_VISUAL_STUDIO 1
/**
 * We want to differentiate carefully between
 * clang under visual studio and regular visual
 * studio.
 */
#ifdef __clang__
// clang under visual studio
#define ADA_CLANG_VISUAL_STUDIO 1
#else
// just regular visual studio (best guess)
#define ADA_REGULAR_VISUAL_STUDIO 1
#endif  // __clang__
#endif  // _MSC_VER

#if defined(__GNUC__)
// Marks a block with a name so that MCA analysis can see it.
#define ADA_BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name);
#define ADA_END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name);
#define ADA_DEBUG_BLOCK(name, block) \
  BEGIN_DEBUG_BLOCK(name);           \
  block;                             \
  END_DEBUG_BLOCK(name);
#else
#define ADA_BEGIN_DEBUG_BLOCK(name)
#define ADA_END_DEBUG_BLOCK(name)
#define ADA_DEBUG_BLOCK(name, block)
#endif

// Align to N-byte boundary
#define ADA_ROUNDUP_N(a, n) (((a) + ((n) - 1)) & ~((n) - 1))
#define ADA_ROUNDDOWN_N(a, n) ((a) & ~((n) - 1))

#define ADA_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n) - 1)) == 0)

#if defined(ADA_REGULAR_VISUAL_STUDIO)

#define ada_really_inline __forceinline
#define ada_never_inline __declspec(noinline)

#define ada_unused
#define ada_warn_unused

#define ADA_PUSH_DISABLE_WARNINGS __pragma(warning(push))
#define ADA_PUSH_DISABLE_ALL_WARNINGS __pragma(warning(push, 0))
#define ADA_DISABLE_VS_WARNING(WARNING_NUMBER) \
  __pragma(warning(disable : WARNING_NUMBER))
// Get rid of Intellisense-only warnings (Code Analysis)
// Though __has_include is C++17, it is supported in Visual Studio 2017 or
// better (_MSC_VER>=1910).
#ifdef __has_include
#if __has_include(<CppCoreCheck\Warnings.h>)
#include <CppCoreCheck\Warnings.h>
#define ADA_DISABLE_UNDESIRED_WARNINGS \
  ADA_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS)
#endif
#endif

#ifndef ADA_DISABLE_UNDESIRED_WARNINGS
#define ADA_DISABLE_UNDESIRED_WARNINGS
#endif

#define ADA_DISABLE_DEPRECATED_WARNING ADA_DISABLE_VS_WARNING(4996)
#define ADA_DISABLE_STRICT_OVERFLOW_WARNING
#define ADA_POP_DISABLE_WARNINGS __pragma(warning(pop))

#else  // ADA_REGULAR_VISUAL_STUDIO

#define ada_really_inline inline __attribute__((always_inline))
#define ada_never_inline inline __attribute__((noinline))

#define ada_unused __attribute__((unused))
#define ada_warn_unused __attribute__((warn_unused_result))

#define ADA_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push")
// gcc doesn't seem to disable all warnings with all and extra, add warnings
// here as necessary
#define ADA_PUSH_DISABLE_ALL_WARNINGS               \
  ADA_PUSH_DISABLE_WARNINGS                         \
  ADA_DISABLE_GCC_WARNING("-Weffc++")               \
  ADA_DISABLE_GCC_WARNING("-Wall")                  \
  ADA_DISABLE_GCC_WARNING("-Wconversion")           \
  ADA_DISABLE_GCC_WARNING("-Wextra")                \
  ADA_DISABLE_GCC_WARNING("-Wattributes")           \
  ADA_DISABLE_GCC_WARNING("-Wimplicit-fallthrough") \
  ADA_DISABLE_GCC_WARNING("-Wnon-virtual-dtor")     \
  ADA_DISABLE_GCC_WARNING("-Wreturn-type")          \
  ADA_DISABLE_GCC_WARNING("-Wshadow")               \
  ADA_DISABLE_GCC_WARNING("-Wunused-parameter")     \
  ADA_DISABLE_GCC_WARNING("-Wunused-variable")      \
  ADA_DISABLE_GCC_WARNING("-Wsign-compare")
#define ADA_PRAGMA(P) _Pragma(#P)
#define ADA_DISABLE_GCC_WARNING(WARNING) \
  ADA_PRAGMA(GCC diagnostic ignored WARNING)
#if defined(ADA_CLANG_VISUAL_STUDIO)
#define ADA_DISABLE_UNDESIRED_WARNINGS \
  ADA_DISABLE_GCC_WARNING("-Wmicrosoft-include")
#else
#define ADA_DISABLE_UNDESIRED_WARNINGS
#endif
#define ADA_DISABLE_DEPRECATED_WARNING \
  ADA_DISABLE_GCC_WARNING("-Wdeprecated-declarations")
#define ADA_DISABLE_STRICT_OVERFLOW_WARNING \
  ADA_DISABLE_GCC_WARNING("-Wstrict-overflow")
#define ADA_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop")

#endif  // MSC_VER

#if defined(ADA_VISUAL_STUDIO)
/**
 * It does not matter here whether you are using
 * the regular visual studio or clang under visual
 * studio.
 */
#if ADA_USING_LIBRARY
#define ADA_DLLIMPORTEXPORT __declspec(dllimport)
#else
#define ADA_DLLIMPORTEXPORT __declspec(dllexport)
#endif
#else
#define ADA_DLLIMPORTEXPORT
#endif

/// If EXPR is an error, returns it.
#define ADA_TRY(EXPR)   \
  {                     \
    auto _err = (EXPR); \
    if (_err) {         \
      return _err;      \
    }                   \
  }

// __has_cpp_attribute is part of C++20
#if !defined(__has_cpp_attribute)
#define __has_cpp_attribute(x) 0
#endif

#if __has_cpp_attribute(gnu::noinline)
#define ADA_ATTRIBUTE_NOINLINE [[gnu::noinline]]
#else
#define ADA_ATTRIBUTE_NOINLINE
#endif

namespace ada {
[[noreturn]] inline void unreachable() {
#ifdef __GNUC__
  __builtin_unreachable();
#elif defined(_MSC_VER)
  __assume(false);
#else
#endif
}
}  // namespace ada

// Unless the programmer has already set ADA_DEVELOPMENT_CHECKS,
// we want to set it under debug builds. We detect a debug build
// under Visual Studio when the _DEBUG macro is set. Under the other
// compilers, we use the fact that they define __OPTIMIZE__ whenever
// they allow optimizations.
// It is possible that this could miss some cases where ADA_DEVELOPMENT_CHECKS
// is helpful, but the programmer can set the macro ADA_DEVELOPMENT_CHECKS.
// It could also wrongly set ADA_DEVELOPMENT_CHECKS (e.g., if the programmer
// sets _DEBUG in a release build under Visual Studio, or if some compiler fails
// to set the __OPTIMIZE__ macro).
#if !defined(ADA_DEVELOPMENT_CHECKS) && !defined(NDEBUG)
#ifdef _MSC_VER
// Visual Studio seems to set _DEBUG for debug builds.
#ifdef _DEBUG
#define ADA_DEVELOPMENT_CHECKS 1
#endif  // _DEBUG
#else   // _MSC_VER
// All other compilers appear to set __OPTIMIZE__ to a positive integer
// when the compiler is optimizing.
#ifndef __OPTIMIZE__
#define ADA_DEVELOPMENT_CHECKS 1
#endif  // __OPTIMIZE__
#endif  // _MSC_VER
#endif  // ADA_DEVELOPMENT_CHECKS

#define ADA_STR(x) #x

#if ADA_DEVELOPMENT_CHECKS
#define ADA_REQUIRE(EXPR) \
  {                       \
    if (!(EXPR) { abort(); }) }

#define ADA_FAIL(MESSAGE)                            \
  do {                                               \
    std::cerr << "FAIL: " << (MESSAGE) << std::endl; \
    abort();                                         \
  } while (0);
#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE)                                    \
  do {                                                                         \
    if (LHS != RHS) {                                                          \
      std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \
      ADA_FAIL(MESSAGE);                                                       \
    }                                                                          \
  } while (0);
#define ADA_ASSERT_TRUE(COND)                                               \
  do {                                                                      \
    if (!(COND)) {                                                          \
      std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \
                << std::endl;                                               \
      ADA_FAIL(ADA_STR(COND));                                              \
    }                                                                       \
  } while (0);
#else
#define ADA_FAIL(MESSAGE)
#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE)
#define ADA_ASSERT_TRUE(COND)
#endif

#ifdef ADA_VISUAL_STUDIO
#define ADA_ASSUME(COND) __assume(COND)
#else
#define ADA_ASSUME(COND)       \
  do {                         \
    if (!(COND)) {             \
      __builtin_unreachable(); \
    }                          \
  } while (0)
#endif

#if defined(__SSSE3__)
#define ADA_SSSE3 1
#endif

#if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \
    (defined(_M_AMD64) || defined(_M_X64) ||                         \
     (defined(_M_IX86_FP) && _M_IX86_FP == 2))
#define ADA_SSE2 1
#endif

// AVX-512 byte/word ops + 128/256-bit vectors of AVX-512 instructions.
// Used for optional high-performance IP address parsing kernels.
#if defined(__AVX512BW__) && defined(__AVX512VL__)
#define ADA_AVX512 1
#endif

#if defined(__aarch64__) || defined(_M_ARM64)
#define ADA_NEON 1
#endif

#if defined(__loongarch_sx)
#define ADA_LSX 1
#endif

#if defined(__riscv_v) && __riscv_v_intrinsic >= 11000
// Support RVV intrinsics v0.11 and above
#define ADA_RVV 1
#endif

#ifndef __has_cpp_attribute
#define ada_lifetime_bound
#elif __has_cpp_attribute(msvc::lifetimebound)
#define ada_lifetime_bound [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
#define ada_lifetime_bound [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
#define ada_lifetime_bound [[lifetimebound]]
#else
#define ada_lifetime_bound
#endif

#ifdef __cpp_lib_format
#if __cpp_lib_format >= 202110L
#include <format>
#define ADA_HAS_FORMAT 1
#endif
#endif

#ifndef ADA_INCLUDE_URL_PATTERN
#define ADA_INCLUDE_URL_PATTERN 1
#endif  // ADA_INCLUDE_URL_PATTERN

#endif  // ADA_COMMON_DEFS_H
/* end file include/ada/common_defs.h */
#include <cstdint>

/**
 * These functions are not part of our public API and may
 * change at any time.
 * @private
 * @namespace ada::character_sets
 * @brief Includes the definitions for unicode character sets.
 */
namespace ada::character_sets {
ada_really_inline constexpr bool bit_at(const uint8_t a[], uint8_t i);
}  // namespace ada::character_sets

#endif  // ADA_CHARACTER_SETS_H
/* end file include/ada/character_sets.h */
/* begin file include/ada/character_sets-inl.h */
/**
 * @file character_sets-inl.h
 * @brief Definitions of the character sets used by unicode functions.
 * @author Node.js
 * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc
 */
#ifndef ADA_CHARACTER_SETS_INL_H
#define ADA_CHARACTER_SETS_INL_H


/**
 * These functions are not part of our public API and may
 * change at any time.
 * @private
 */
namespace ada::character_sets {

constexpr char hex[1024] =
    "%00\0%01\0%02\0%03\0%04\0%05\0%06\0%07\0"
    "%08\0%09\0%0A\0%0B\0%0C\0%0D\0%0E\0%0F\0"
    "%10\0%11\0%12\0%13\0%14\0%15\0%16\0%17\0"
    "%18\0%19\0%1A\0%1B\0%1C\0%1D\0%1E\0%1F\0"
    "%20\0%21\0%22\0%23\0%24\0%25\0%26\0%27\0"
    "%28\0%29\0%2A\0%2B\0%2C\0%2D\0%2E\0%2F\0"
    "%30\0%31\0%32\0%33\0%34\0%35\0%36\0%37\0"
    "%38\0%39\0%3A\0%3B\0%3C\0%3D\0%3E\0%3F\0"
    "%40\0%41\0%42\0%43\0%44\0%45\0%46\0%47\0"
    "%48\0%49\0%4A\0%4B\0%4C\0%4D\0%4E\0%4F\0"
    "%50\0%51\0%52\0%53\0%54\0%55\0%56\0%57\0"
    "%58\0%59\0%5A\0%5B\0%5C\0%5D\0%5E\0%5F\0"
    "%60\0%61\0%62\0%63\0%64\0%65\0%66\0%67\0"
    "%68\0%69\0%6A\0%6B\0%6C\0%6D\0%6E\0%6F\0"
    "%70\0%71\0%72\0%73\0%74\0%75\0%76\0%77\0"
    "%78\0%79\0%7A\0%7B\0%7C\0%7D\0%7E\0%7F\0"
    "%80\0%81\0%82\0%83\0%84\0%85\0%86\0%87\0"
    "%88\0%89\0%8A\0%8B\0%8C\0%8D\0%8E\0%8F\0"
    "%90\0%91\0%92\0%93\0%94\0%95\0%96\0%97\0"
    "%98\0%99\0%9A\0%9B\0%9C\0%9D\0%9E\0%9F\0"
    "%A0\0%A1\0%A2\0%A3\0%A4\0%A5\0%A6\0%A7\0"
    "%A8\0%A9\0%AA\0%AB\0%AC\0%AD\0%AE\0%AF\0"
    "%B0\0%B1\0%B2\0%B3\0%B4\0%B5\0%B6\0%B7\0"
    "%B8\0%B9\0%BA\0%BB\0%BC\0%BD\0%BE\0%BF\0"
    "%C0\0%C1\0%C2\0%C3\0%C4\0%C5\0%C6\0%C7\0"
    "%C8\0%C9\0%CA\0%CB\0%CC\0%CD\0%CE\0%CF\0"
    "%D0\0%D1\0%D2\0%D3\0%D4\0%D5\0%D6\0%D7\0"
    "%D8\0%D9\0%DA\0%DB\0%DC\0%DD\0%DE\0%DF\0"
    "%E0\0%E1\0%E2\0%E3\0%E4\0%E5\0%E6\0%E7\0"
    "%E8\0%E9\0%EA\0%EB\0%EC\0%ED\0%EE\0%EF\0"
    "%F0\0%F1\0%F2\0%F3\0%F4\0%F5\0%F6\0%F7\0"
    "%F8\0%F9\0%FA\0%FB\0%FC\0%FD\0%FE\0%FF";

constexpr uint8_t C0_CONTROL_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 40     41     42     43     44     45     46     47
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t SPECIAL_QUERY_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x80,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
    // 40     41     42     43     44     45     46     47
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t QUERY_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
    // 40     41     42     43     44     45     46     47
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t FRAGMENT_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x01 | 0x00 | 0x04 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
    // 40     41     42     43     44     45     46     47
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t USERINFO_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 40     41     42     43     44     45     46     47
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t PATH_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x80,
    // 40     41     42     43     44     45     46     47
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x40 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x00 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

constexpr uint8_t WWW_FORM_URLENCODED_PERCENT_ENCODE[32] = {
    // 00     01     02     03     04     05     06     07
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 08     09     0A     0B     0C     0D     0E     0F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 10     11     12     13     14     15     16     17
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 18     19     1A     1B     1C     1D     1E     1F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 20     21     22     23     24     25     26     27
    0x00 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 28     29     2A     2B     2C     2D     2E     2F
    0x01 | 0x02 | 0x00 | 0x08 | 0x10 | 0x00 | 0x00 | 0x80,
    // 30     31     32     33     34     35     36     37
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 38     39     3A     3B     3C     3D     3E     3F
    0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 40     41     42     43     44     45     46     47
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 48     49     4A     4B     4C     4D     4E     4F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 50     51     52     53     54     55     56     57
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 58     59     5A     5B     5C     5D     5E     5F
    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00,
    // 60     61     62     63     64     65     66     67
    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 68     69     6A     6B     6C     6D     6E     6F
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 70     71     72     73     74     75     76     77
    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
    // 78     79     7A     7B     7C     7D     7E     7F
    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 80     81     82     83     84     85     86     87
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 88     89     8A     8B     8C     8D     8E     8F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 90     91     92     93     94     95     96     97
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // 98     99     9A     9B     9C     9D     9E     9F
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A0     A1     A2     A3     A4     A5     A6     A7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // A8     A9     AA     AB     AC     AD     AE     AF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B0     B1     B2     B3     B4     B5     B6     B7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // B8     B9     BA     BB     BC     BD     BE     BF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C0     C1     C2     C3     C4     C5     C6     C7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // C8     C9     CA     CB     CC     CD     CE     CF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D0     D1     D2     D3     D4     D5     D6     D7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // D8     D9     DA     DB     DC     DD     DE     DF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E0     E1     E2     E3     E4     E5     E6     E7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // E8     E9     EA     EB     EC     ED     EE     EF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F0     F1     F2     F3     F4     F5     F6     F7
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
    // F8     F9     FA     FB     FC     FD     FE     FF
    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};

ada_really_inline constexpr bool bit_at(const uint8_t a[], const uint8_t i) {
  return !!(a[i >> 3] & (1 << (i & 7)));
}

}  // namespace ada::character_sets

#endif  // ADA_CHARACTER_SETS_INL_H
/* end file include/ada/character_sets-inl.h */
/* begin file include/ada/checkers-inl.h */
/**
 * @file checkers-inl.h
 * @brief Definitions for URL specific checkers used within Ada.
 */
#ifndef ADA_CHECKERS_INL_H
#define ADA_CHECKERS_INL_H

#include <bit>
#include <cstdint>
#include <cstring>
#include <string_view>
/* begin file include/ada/checkers.h */
/**
 * @file checkers.h
 * @brief Declarations for URL specific checkers used within Ada.
 */
#ifndef ADA_CHECKERS_H
#define ADA_CHECKERS_H


#include <cstring>
#include <string_view>

/**
 * These functions are not part of our public API and may
 * change at any time.
 * @private
 * @namespace ada::checkers
 * @brief Includes the definitions for validation functions
 */
namespace ada::checkers {

/**
 * @private
 * Assuming that x is an ASCII letter, this function returns the lower case
 * equivalent.
 * @details More likely to be inlined by the compiler and constexpr.
 */
constexpr char to_lower(char x) noexcept;

/**
 * @private
 * Returns true if the character is an ASCII letter. Equivalent to std::isalpha
 * but more likely to be inlined by the compiler.
 *
 * @attention std::isalpha is not constexpr generally.
 */
constexpr bool is_alpha(char x) noexcept;

/**
 * @private
 * Check whether a string starts with 0x or 0X. The function is only
 * safe if input.size() >=2.
 *
 * @see has_hex_prefix
 */
constexpr bool has_hex_prefix_unsafe(std::string_view input);
/**
 * @private
 * Check whether a string starts with 0x or 0X.
 */
constexpr bool has_hex_prefix(std::string_view input);

/**
 * @private
 * Check whether x is an ASCII digit. More likely to be inlined than
 * std::isdigit.
 */
constexpr bool is_digit(char x) noexcept;

/**
 * @private
 * @details A string starts with a Windows drive letter if all of the following
 * are true:
 *
 *   - its length is greater than or equal to 2
 *   - its first two code points are a Windows drive letter
 *   - its length is 2 or its third code point is U+002F (/), U+005C (\), U+003F
 * (?), or U+0023 (#).
 *
 * https://url.spec.whatwg.org/#start-with-a-windows-drive-letter
 */
inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept;

/**
 * @private
 * @details A normalized Windows drive letter is a Windows drive letter of which
 * the second code point is U+003A (:).
 */
inline constexpr bool is_normalized_windows_drive_letter(
    std::string_view input) noexcept;

/**
 * @private
 * Returns true if an input is an ipv4 address. It is assumed that the string
 * does not contain uppercase ASCII characters (the input should have been
 * lowered cased before calling this function) and is not empty.
 */
ada_really_inline constexpr bool is_ipv4(std::string_view view) noexcept;

/**
 * @private
 * Returns a bitset. If the first bit is set, then at least one character needs
 * percent encoding. If the second bit is set, a \\ is found. If the third bit
 * is set then we have a dot. If the fourth bit is set, then we have a percent
 * character.
 */
ada_really_inline constexpr uint8_t path_signature(
    std::string_view input) noexcept;

/**
 * @private
 * Returns true if the length of the domain name and its labels are according to
 * the specifications. The length of the domain must be 255 octets (253
 * characters not including the last 2 which are the empty label reserved at the
 * end). When the empty label is included (a dot at the end), the domain name
 * can have 254 characters. The length of a label must be at least 1 and at most
 * 63 characters.
 * @see section 3.1. of https://www.rfc-editor.org/rfc/rfc1034
 * @see https://www.unicode.org/reports/tr46/#ToASCII
 */
ada_really_inline constexpr bool verify_dns_length(
    std::string_view input) noexcept;

/**
 * @private
 * Fast-path parser for pure decimal IPv4 addresses (e.g., "192.168.1.1").
 * Returns the packed 32-bit IPv4 address on success, or a value > 0xFFFFFFFF
 * to indicate failure (caller should fall back to general parser).
 * This is optimized for the common case where the input is a well-formed
 * decimal IPv4 address with exactly 4 octets.
 */
ada_really_inline uint64_t try_parse_ipv4_fast(std::string_view input) noexcept;

/**
 * Sentinel value indicating try_parse_ipv4_fast() did not succeed.
 * Any value > 0xFFFFFFFF indicates the fast path should not be used.
 */
constexpr uint64_t ipv4_fast_fail = uint64_t(1) << 32;

}  // namespace ada::checkers

#endif  // ADA_CHECKERS_H
/* end file include/ada/checkers.h */

#if defined(ADA_AVX512)
#include <immintrin.h>
#endif

namespace ada::checkers {

constexpr bool has_hex_prefix_unsafe(std::string_view input) {
  // This is actually efficient code, see has_hex_prefix for the assembly.
  constexpr bool is_little_endian = std::endian::native == std::endian::little;
  constexpr uint16_t word0x = 0x7830;
  uint16_t two_first_bytes =
      static_cast<uint16_t>(input[0]) |
      static_cast<uint16_t>((static_cast<uint16_t>(input[1]) << 8));
  if constexpr (is_little_endian) {
    two_first_bytes |= 0x2000;
  } else {
    two_first_bytes |= 0x020;
  }
  return two_first_bytes == word0x;
}

constexpr bool has_hex_prefix(std::string_view input) {
  return input.size() >= 2 && has_hex_prefix_unsafe(input);
}

constexpr bool is_digit(char x) noexcept { return (x >= '0') & (x <= '9'); }

constexpr char to_lower(char x) noexcept { return (x | 0x20); }

constexpr bool is_alpha(char x) noexcept {
  return (to_lower(x) >= 'a') && (to_lower(x) <= 'z');
}

constexpr bool is_windows_drive_letter(std::string_view input) noexcept {
  return input.size() >= 2 &&
         (is_alpha(input[0]) && ((input[1] == ':') || (input[1] == '|'))) &&
         ((input.size() == 2) || (input[2] == '/' || input[2] == '\\' ||
                                  input[2] == '?' || input[2] == '#'));
}

constexpr bool is_normalized_windows_drive_letter(
    std::string_view input) noexcept {
  return input.size() == 2 && (is_alpha(input[0]) && (input[1] == ':'));
}

namespace detail {

// Unrolled pure-decimal IPv4. The common portable path for 7-16 byte hosts.
ada_really_inline uint64_t
parse_ipv4_decimal_scalar(const char* p, const char* pend) noexcept {
  uint32_t ipv4 = 0;
  for (int i = 0; i < 4; ++i) {
    if (p == pend) [[unlikely]] {
      return ipv4_fast_fail;
    }
    uint32_t val;
    char c = *p;
    if (c >= '0' && c <= '9') [[likely]] {
      val = static_cast<uint32_t>(c - '0');
      ++p;
    } else {
      return ipv4_fast_fail;
    }
    if (p < pend) {
      c = *p;
      if (c >= '0' && c <= '9') {
        if (val == 0) [[unlikely]] {
          return ipv4_fast_fail;
        }
        val = val * 10u + static_cast<uint32_t>(c - '0');
        ++p;
        if (p < pend) {
          c = *p;
          if (c >= '0' && c <= '9') {
            val = val * 10u + static_cast<uint32_t>(c - '0');
            ++p;
            if (val > 255u) [[unlikely]] {
              return ipv4_fast_fail;
            }
          }
        }
      }
    }
    ipv4 = (ipv4 << 8) | val;
    if (i < 3) {
      if (p == pend || *p != '.') [[unlikely]] {
        return ipv4_fast_fail;
      }
      ++p;
    }
  }
  if (p != pend) {
    if (p == pend - 1 && *p == '.') {
      return ipv4;
    }
    return ipv4_fast_fail;
  }
  return ipv4;
}

#if defined(ADA_AVX512)
// After SIMD validation: fewer rejection branches on convert.
ada_really_inline uint64_t
parse_ipv4_decimal_trusted(const char* p, const char* pend) noexcept {
  uint32_t ipv4 = 0;
  for (int i = 0; i < 4; ++i) {
    uint32_t val = static_cast<uint32_t>(*p - '0');
    ++p;
    if (p < pend && static_cast<unsigned char>(*p - '0') <= 9) {
      if (val == 0) [[unlikely]] {
        return ipv4_fast_fail;
      }
      val = val * 10u + static_cast<uint32_t>(*p - '0');
      ++p;
      if (p < pend && static_cast<unsigned char>(*p - '0') <= 9) {
        val = val * 10u + static_cast<uint32_t>(*p - '0');
        ++p;
        if (val > 255u) [[unlikely]] {
          return ipv4_fast_fail;
        }
      }
    }
    ipv4 = (ipv4 << 8) | val;
    if (i < 3) {
      ++p;  // trusted '.'
    }
  }
  return ipv4;  // trailing-dot already accounted for by caller via pend
}

// AVX-512 pure-decimal IPv4 (Lemire/Mula-style masked load + parallel checks).
// No over-read of the source string. Wins when the binary is built with
// -mavx512bw -mavx512vl (or -march that enables them).
ada_really_inline uint64_t try_parse_ipv4_avx512(const char* data,
                                                 size_t len) noexcept {
  const __mmask16 live = static_cast<__mmask16>((1u << len) - 1u);
  const __m128i input =
      _mm_maskz_loadu_epi8(live, reinterpret_cast<const void*>(data));
  const __mmask16 is_dot =
      _mm_mask_cmpeq_epi8_mask(live, input, _mm_set1_epi8('.'));
  const __m128i shifted = _mm_sub_epi8(input, _mm_set1_epi8('0'));
  const __mmask16 is_digit =
      _mm_mask_cmplt_epu8_mask(live, shifted, _mm_set1_epi8(10));
  if ((is_digit | is_dot) != live) {
    return ipv4_fast_fail;
  }
  const unsigned dot_count =
      static_cast<unsigned>(_mm_popcnt_u32(static_cast<unsigned>(is_dot)));
  size_t effective_len = len;
  if (dot_count == 3) {
    // ok
  } else if (dot_count == 4 && data[len - 1] == '.') {
    effective_len = len - 1;  // strip trailing dot for convert
  } else {
    return ipv4_fast_fail;
  }
  // Convert from a tiny stack copy so trusted peeks stay in-bounds.
  alignas(16) char buf[16]{};
  std::memcpy(buf, data, effective_len);
  return parse_ipv4_decimal_trusted(buf, buf + effective_len);
}
#endif  // ADA_AVX512

}  // namespace detail

/**
 * Fast pure-decimal IPv4 parse. Returns packed address or ipv4_fast_fail.
 * Accepts an optional single trailing dot.
 *
 * On AVX-512BW+VL targets, uses a masked-load SIMD kernel (no source
 * over-read) inspired by Lemire/Mula. Otherwise uses an unrolled scalar path
 * (typically faster than SSE2/NEON pre-validation for these 7-16 byte hosts).
 */
ada_really_inline uint64_t
try_parse_ipv4_fast(std::string_view input) noexcept {
  const size_t len = input.size();
  // Shortest pure decimal: "0.0.0.0" (7). Longest + trailing dot: 16.
  if (len < 7 || len > 16) [[unlikely]] {
    return ipv4_fast_fail;
  }
  const char* data = input.data();

#if defined(ADA_AVX512)
  return detail::try_parse_ipv4_avx512(data, len);
#else
  return detail::parse_ipv4_decimal_scalar(data, data + len);
#endif
}

}  // namespace ada::checkers

#endif  // ADA_CHECKERS_INL_H
/* end file include/ada/checkers-inl.h */
/* begin file include/ada/log.h */
/**
 * @file log.h
 * @brief Includes the definitions for logging.
 * @private Excluded from docs through the doxygen file.
 */
#ifndef ADA_LOG_H
#define ADA_LOG_H

// To enable logging, set ADA_LOGGING to 1:
#ifndef ADA_LOGGING
#define ADA_LOGGING 0
#endif

#if ADA_LOGGING
#include <iostream>
#endif  // ADA_LOGGING

namespace ada {

/**
 * Log a message. If you want to have no overhead when logging is disabled, use
 * the ada_log macro.
 * @private
 */
template <typename... Args>
constexpr ada_really_inline void log([[maybe_unused]] Args... args) {
#if ADA_LOGGING
  ((std::cout << "ADA_LOG: ") << ... << args) << std::endl;
#endif  // ADA_LOGGING
}
}  // namespace ada

#if ADA_LOGGING
#ifndef ada_log
#define ada_log(...)       \
  do {                     \
    ada::log(__VA_ARGS__); \
  } while (0)
#endif  // ada_log
#else
#define ada_log(...)
#endif  // ADA_LOGGING

#endif  // ADA_LOG_H
/* end file include/ada/log.h */
/* begin file include/ada/encoding_type.h */
/**
 * @file encoding_type.h
 * @brief Character encoding type definitions.
 *
 * Defines the encoding types supported for URL processing.
 *
 * @see https://encoding.spec.whatwg.org/
 */
#ifndef ADA_ENCODING_TYPE_H
#define ADA_ENCODING_TYPE_H

#include <string>

namespace ada {

/**
 * @brief Character encoding types for URL processing.
 *
 * Specifies the character encoding used for percent-decoding and other
 * string operations. UTF-8 is the most commonly used encoding for URLs.
 *
 * @see https://encoding.spec.whatwg.org/#encodings
 */
enum class encoding_type {
  UTF8,     /**< UTF-8 encoding (default for URLs) */
  UTF_16LE, /**< UTF-16 Little Endian encoding */
  UTF_16BE, /**< UTF-16 Big Endian encoding */
};

/**
 * Converts an encoding_type to its string representation.
 * @param type The encoding type to convert.
 * @return A string view of the encoding name.
 */
ada_warn_unused std::string_view to_string(encoding_type type);

}  // namespace ada

#endif  // ADA_ENCODING_TYPE_H
/* end file include/ada/encoding_type.h */
/* begin file include/ada/helpers.h */
/**
 * @file helpers.h
 * @brief Definitions for helper functions used within Ada.
 */
#ifndef ADA_HELPERS_H
#define ADA_HELPERS_H

/* begin file include/ada/url_base.h */
/**
 * @file url_base.h
 * @brief Base class and common definitions for URL types.
 *
 * This file defines the `url_base` abstract base class from which both
 * `ada::url` and `ada::url_aggregator` inherit. It also defines common
 * enumerations like `url_host_type`.
 */
#ifndef ADA_URL_BASE_H
#define ADA_URL_BASE_H

/* begin file include/ada/scheme.h */
/**
 * @file scheme.h
 * @brief URL scheme type definitions and utilities.
 *
 * This header defines the URL scheme types (http, https, etc.) and provides
 * functions to identify special schemes and their default ports according
 * to the WHATWG URL Standard.
 *
 * @see https://url.spec.whatwg.org/#special-scheme
 */
#ifndef ADA_SCHEME_H
#define ADA_SCHEME_H


#include <string>

/**
 * @namespace ada::scheme
 * @brief URL scheme utilities and constants.
 *
 * Provides functions for working with URL schemes, including identification
 * of special schemes and retrieval of default port numbers.
 */
namespace ada::scheme {

/**
 * @brief Enumeration of URL scheme types.
 *
 * Special schemes have specific parsing rules and default ports.
 * Using an enum allows efficient scheme comparisons without string operations.
 *
 * Default ports:
 * - HTTP: 80
 * - HTTPS: 443
 * - WS: 80
 * - WSS: 443
 * - FTP: 21
 * - FILE: (none)
 */
enum type : uint8_t {
  HTTP = 0,        /**< http:// scheme (port 80) */
  NOT_SPECIAL = 1, /**< Non-special scheme (no default port) */
  HTTPS = 2,       /**< https:// scheme (port 443) */
  WS = 3,          /**< ws:// WebSocket scheme (port 80) */
  FTP = 4,         /**< ftp:// scheme (port 21) */
  WSS = 5,         /**< wss:// secure WebSocket scheme (port 443) */
  FILE = 6         /**< file:// scheme (no default port) */
};

/**
 * Checks if a scheme string is a special scheme.
 * @param scheme The scheme string to check (e.g., "http", "https").
 * @return `true` if the scheme is special, `false` otherwise.
 * @see https://url.spec.whatwg.org/#special-scheme
 */
ada_really_inline constexpr bool is_special(std::string_view scheme);

/**
 * Returns the default port for a special scheme string.
 * @param scheme The scheme string (e.g., "http", "https").
 * @return The default port number, or 0 if not a special scheme.
 * @see https://url.spec.whatwg.org/#special-scheme
 */
constexpr uint16_t get_special_port(std::string_view scheme) noexcept;

/**
 * Returns the default port for a scheme type.
 * @param type The scheme type enum value.
 * @return The default port number, or 0 if not applicable.
 * @see https://url.spec.whatwg.org/#special-scheme
 */
constexpr uint16_t get_special_port(ada::scheme::type type) noexcept;

/**
 * Converts a scheme string to its type enum.
 * @param scheme The scheme string to convert.
 * @return The corresponding scheme type, or NOT_SPECIAL if not recognized.
 */
constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept;

}  // namespace ada::scheme

#endif  // ADA_SCHEME_H
/* end file include/ada/scheme.h */

#include <string>
#include <string_view>

namespace ada {

/**
 * @brief Enum representing the type of host in a URL.
 *
 * Used to distinguish between regular domain names, IPv4 addresses,
 * and IPv6 addresses for proper parsing and serialization.
 */
enum url_host_type : uint8_t {
  /** Regular domain name (e.g., "www.example.com") */
  DEFAULT = 0,
  /** IPv4 address (e.g., "127.0.0.1") */
  IPV4 = 1,
  /** IPv6 address (e.g., "[::1]" or "[2001:db8::1]") */
  IPV6 = 2,
};

/**
 * @brief Abstract base class for URL representations.
 *
 * The `url_base` class provides the common interface and state shared by
 * both `ada::url` and `ada::url_aggregator`. It contains basic URL attributes
 * like validity status and scheme type, but delegates component storage and
 * access to derived classes.
 *
 * @note This is an abstract class and cannot be instantiated directly.
 *       Use `ada::url` or `ada::url_aggregator` instead.
 *
 * @see url
 * @see url_aggregator
 */
struct url_base {
  virtual ~url_base() = default;

  /**
   * Indicates whether the URL was successfully parsed.
   * Set to `false` if parsing failed (e.g., invalid URL syntax).
   */
  bool is_valid{true};

  /**
   * Indicates whether the URL has an opaque path (non-hierarchical).
   * Opaque paths occur in non-special URLs like `mailto:` or `javascript:`.
   */
  bool has_opaque_path{false};

  /**
   * The type of the URL's host (domain, IPv4, or IPv6).
   */
  url_host_type host_type = url_host_type::DEFAULT;

  /**
   * @private
   * Internal representation of the URL's scheme type.
   */
  ada::scheme::type type{ada::scheme::type::NOT_SPECIAL};

  /**
   * Checks if the URL has a special scheme (http, https, ws, wss, ftp, file).
   * Special schemes have specific parsing rules and default ports.
   * @return `true` if the scheme is special, `false` otherwise.
   */
  [[nodiscard]] ada_really_inline constexpr bool is_special() const noexcept;

  /**
   * Returns the URL's origin (scheme + host + port for special URLs).
   * @return A newly allocated string containing the serialized origin.
   * @see https://url.spec.whatwg.org/#concept-url-origin
   */
  [[nodiscard]] virtual std::string get_origin() const = 0;

  /**
   * Validates whether the hostname is a valid domain according to RFC 1034.
   * Checks that the domain and its labels have valid lengths.
   * @return `true` if the domain is valid, `false` otherwise.
   */
  [[nodiscard]] virtual bool has_valid_domain() const noexcept = 0;

  /**
   * @private
   * Returns the default port for special schemes (e.g., 443 for https).
   * Returns 0 for file:// URLs or non-special schemes.
   */
  [[nodiscard]] inline uint16_t get_special_port() const noexcept;

  /**
   * @private
   * Returns the default port for the URL's scheme, or 0 if none.
   */
  [[nodiscard]] ada_really_inline uint16_t scheme_default_port() const noexcept;

  /**
   * @private
   * Parses a port number from the input string.
   * @param view The string containing the port to parse.
   * @param check_trailing_content Whether to validate no trailing characters.
   * @return Number of bytes consumed on success, 0 on failure.
   */
  virtual size_t parse_port(std::string_view view,
                            bool check_trailing_content) = 0;

  /** @private */
  virtual ada_really_inline size_t parse_port(std::string_view view) {
    return this->parse_port(view, false);
  }

  /**
   * Returns a JSON string representation of this URL for debugging.
   * @return A JSON-formatted string with URL information.
   */
  [[nodiscard]] virtual std::string to_string() const = 0;

  /** @private */
  virtual inline void clear_pathname() = 0;

  /** @private */
  virtual inline void clear_search() = 0;

  /** @private */
  [[nodiscard]] virtual inline bool has_hash() const noexcept = 0;

  /** @private */
  [[nodiscard]] virtual inline bool has_search() const noexcept = 0;

};  // url_base

}  // namespace ada

#endif
/* end file include/ada/url_base.h */

#include <string>
#include <string_view>
#include <optional>

#if ADA_DEVELOPMENT_CHECKS
#include <iostream>
#endif  // ADA_DEVELOPMENT_CHECKS

/**
 * These functions are not part of our public API and may
 * change at any time.
 *
 * @private
 * @namespace ada::helpers
 * @brief Includes the definitions for helper functions
 */
namespace ada::helpers {

/**
 * @private
 */
template <typename out_iter>
void encode_json(std::string_view view, out_iter out);

/**
 * @private
 * This function is used to prune a fragment from a url, and returning the
 * removed string if input has fragment.
 *
 * @details prune_hash seeks the first '#' and returns everything after it
 * as a string_view, and modifies (in place) the input so that it points at
 * everything before the '#'. If no '#' is found, the input is left unchanged
 * and std::nullopt is returned.
 *
 * @attention The function is non-allocating and it does not throw.
 * @returns Note that the returned string_view might be empty!
 */
ada_really_inline std::optional<std::string_view> prune_hash(
    std::string_view& input) noexcept;

/**
 * @private
 * Defined by the URL specification, shorten a URLs paths.
 * @see https://url.spec.whatwg.org/#shorten-a-urls-path
 * @returns Returns true if path is shortened.
 */
ada_really_inline bool shorten_path(std::string& path, ada::scheme::type type);

/**
 * @private
 * Defined by the URL specification, shorten a URLs paths.
 * @see https://url.spec.whatwg.org/#shorten-a-urls-path
 * @returns Returns true if path is shortened.
 */
ada_really_inline bool shorten_path(std::string_view& path,
                                    ada::scheme::type type);

/**
 * @private
 *
 * Parse the path from the provided input and append to the existing
 * (possibly empty) path. The input cannot contain tabs and spaces: it
 * is the user's responsibility to check.
 *
 * The input is expected to be UTF-8.
 *
 * @see https://url.spec.whatwg.org/
 */
ada_really_inline void parse_prepared_path(std::string_view input,
                                           ada::scheme::type type,
                                           std::string& path);

/**
 * @private
 * Remove and mutate all ASCII tab or newline characters from an input.
 */
ada_really_inline void remove_ascii_tab_or_newline(std::string& input);

/**
 * @private
 * Return the substring from input going from index pos to the end.
 */
ada_really_inline constexpr std::string_view substring(std::string_view input,
                                                       size_t pos);

/**
 * @private
 * Returns true if the string_view points within the string.
 */
bool overlaps(std::string_view input1, const std::string& input2) noexcept;

/**
 * @private
 * Return the substring from input going from index pos1 to the pos2 (non
 * included). The length of the substring is pos2 - pos1.
 */
ada_really_inline constexpr std::string_view substring(std::string_view input,
                                                       size_t pos1,
                                                       size_t pos2) {
#if ADA_DEVELOPMENT_CHECKS
  if (pos2 < pos1) {
    std::cerr << "Negative-length substring: [" << pos1 << " to " << pos2 << ")"
              << std::endl;
    abort();
  }
#endif
  return input.substr(pos1, pos2 - pos1);
}

/**
 * @private
 * Modify the string_view so that it has the new size pos, assuming that pos <=
 * input.size(). This function cannot throw.
 */
ada_really_inline void resize(std::string_view& input, size_t pos) noexcept;

/**
 * @private
 * Returns a host's delimiter location depending on the state of the instance,
 * and whether a colon was found outside brackets. Used by the host parser.
 */
ada_really_inline std::pair<size_t, bool> get_host_delimiter_location(
    bool is_special, std::string_view& view) noexcept;

/**
 * @private
 * Removes leading and trailing C0 control and whitespace characters from
 * string.
 */
void trim_c0_whitespace(std::string_view& input) noexcept;

/**
 * @private
 * @see
 * https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path
 */
template <class url_type>
ada_really_inline void strip_trailing_spaces_from_opaque_path(url_type& url);

/**
 * @private
 * Finds the delimiter of a view in authority state.
 */
ada_really_inline size_t
find_authority_delimiter_special(std::string_view view) noexcept;

/**
 * @private
 * Finds the delimiter of a view in authority state.
 */
ada_really_inline size_t
find_authority_delimiter(std::string_view view) noexcept;

/**
 * @private
 */
template <typename T, typename... Args>
inline void inner_concat(std::string& buffer, T t) {
  buffer.append(t);
}

/**
 * @private
 */
template <typename T, typename... Args>
inline void inner_concat(std::string& buffer, T t, Args... args) {
  buffer.append(t);
  return inner_concat(buffer, args...);
}

/**
 * @private
 * Concatenate the arguments and return a string.
 * @returns a string
 */
template <typename... Args>
std::string concat(Args... args) {
  std::string answer;
  inner_concat(answer, args...);
  return answer;
}

/**
 * @private
 * @return Number of leading zeroes.
 */
inline int leading_zeroes(uint32_t input_num) noexcept {
#if ADA_REGULAR_VISUAL_STUDIO
  unsigned long leading_zero(0);
  unsigned long in(input_num);
  return _BitScanReverse(&leading_zero, in) ? int(31 - leading_zero) : 32;
#else
  return __builtin_clz(input_num);
#endif  // ADA_REGULAR_VISUAL_STUDIO
}

/**
 * @private
 * Counts the number of decimal digits necessary to represent x.
 * faster than std::to_string(x).size().
 * @return digit count
 */
inline int fast_digit_count(uint32_t x) noexcept {
  auto int_log2 = [](uint32_t z) -> int {
    return 31 - ada::helpers::leading_zeroes(z | 1);
  };
  // Compiles to very few instructions. Note that the
  // table is static and thus effectively a constant.
  // We leave it inside the function because it is meaningless
  // outside of it (this comes at no performance cost).
  const static uint64_t table[] = {
      4294967296,  8589934582,  8589934582,  8589934582,  12884901788,
      12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
      21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
      25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
      34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
      38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
      42949672960, 42949672960};
  return int((x + table[int_log2(x)]) >> 32);
}
}  // namespace ada::helpers

#endif  // ADA_HELPERS_H
/* end file include/ada/helpers.h */
/* begin file include/ada/parser.h */
/**
 * @file parser.h
 * @brief Low-level URL parsing functions.
 *
 * This header provides the internal URL parsing implementation. Most users
 * should use `ada::parse()` from implementation.h instead of these functions
 * directly.
 *
 * @see implementation.h for the recommended public API
 */
#ifndef ADA_PARSER_H
#define ADA_PARSER_H

#include <string_view>
#include <variant>

/* begin file include/ada/expected.h */
/**
 * @file expected.h
 * @brief Definitions for std::expected
 * @private Excluded from docs through the doxygen file.
 */
///
// expected - An implementation of std::expected with extensions
// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama)
//
// Documentation available at http://tl.tartanllama.xyz/
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to the
// public domain worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software. If not, see
// <http://creativecommons.org/publicdomain/zero/1.0/>.
///

#ifndef TL_EXPECTED_HPP
#define TL_EXPECTED_HPP

#define TL_EXPECTED_VERSION_MAJOR 1
#define TL_EXPECTED_VERSION_MINOR 1
#define TL_EXPECTED_VERSION_PATCH 0

#include <exception>
#include <functional>
#include <type_traits>
#include <utility>

#if defined(__EXCEPTIONS) || defined(_CPPUNWIND)
#define TL_EXPECTED_EXCEPTIONS_ENABLED
#endif

#if (defined(_MSC_VER) && _MSC_VER == 1900)
#define TL_EXPECTED_MSVC2015
#define TL_EXPECTED_MSVC2015_CONSTEXPR
#else
#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr
#endif

#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
     !defined(__clang__))
#define TL_EXPECTED_GCC49
#endif

#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \
     !defined(__clang__))
#define TL_EXPECTED_GCC54
#endif

#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \
     !defined(__clang__))
#define TL_EXPECTED_GCC55
#endif

#if !defined(TL_ASSERT)
// can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug
#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49)
#include <cassert>
#define TL_ASSERT(x) assert(x)
#else
#define TL_ASSERT(x)
#endif
#endif

#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
     !defined(__clang__))
// GCC < 5 doesn't support overloading on const&& for member functions

#define TL_EXPECTED_NO_CONSTRR
// GCC < 5 doesn't support some standard C++11 type traits
#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
  std::has_trivial_copy_constructor<T>
#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
  std::has_trivial_copy_assign<T>

// This one will be different for GCC 5.7 if it's ever supported
#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
  std::is_trivially_destructible<T>

// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks
// std::vector for non-copyable types
#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__))
#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX
#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX
namespace tl {
namespace detail {
template <class T>
struct is_trivially_copy_constructible
    : std::is_trivially_copy_constructible<T> {};
#ifdef _GLIBCXX_VECTOR
template <class T, class A>
struct is_trivially_copy_constructible<std::vector<T, A>> : std::false_type {};
#endif
}  // namespace detail
}  // namespace tl
#endif

#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
  tl::detail::is_trivially_copy_constructible<T>
#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
  std::is_trivially_copy_assignable<T>
#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
  std::is_trivially_destructible<T>
#else
#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
  std::is_trivially_copy_constructible<T>
#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
  std::is_trivially_copy_assignable<T>
#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
  std::is_trivially_destructible<T>
#endif

#if __cplusplus > 201103L
#define TL_EXPECTED_CXX14
#endif

#ifdef TL_EXPECTED_GCC49
#define TL_EXPECTED_GCC49_CONSTEXPR
#else
#define TL_EXPECTED_GCC49_CONSTEXPR constexpr
#endif

#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \
     defined(TL_EXPECTED_GCC49))
#define TL_EXPECTED_11_CONSTEXPR
#else
#define TL_EXPECTED_11_CONSTEXPR constexpr
#endif

namespace tl {
template <class T, class E>
class expected;

#ifndef TL_MONOSTATE_INPLACE_MUTEX
#define TL_MONOSTATE_INPLACE_MUTEX
class monostate {};

struct in_place_t {
  explicit in_place_t() = default;
};
static constexpr in_place_t in_place{};
#endif

template <class E>
class unexpected {
 public:
  static_assert(!std::is_same<E, void>::value, "E must not be void");

  unexpected() = delete;
  constexpr explicit unexpected(const E& e) : m_val(e) {}

  constexpr explicit unexpected(E&& e) : m_val(std::move(e)) {}

  template <class... Args, typename std::enable_if<std::is_constructible<
                               E, Args&&...>::value>::type* = nullptr>
  constexpr explicit unexpected(Args&&... args)
      : m_val(std::forward<Args>(args)...) {}
  template <
      class U, class... Args,
      typename std::enable_if<std::is_constructible<
          E, std::initializer_list<U>&, Args&&...>::value>::type* = nullptr>
  constexpr explicit unexpected(std::initializer_list<U> l, Args&&... args)
      : m_val(l, std::forward<Args>(args)...) {}

  constexpr const E& value() const& { return m_val; }
  TL_EXPECTED_11_CONSTEXPR E& value() & { return m_val; }
  TL_EXPECTED_11_CONSTEXPR E&& value() && { return std::move(m_val); }
  constexpr const E&& value() const&& { return std::move(m_val); }

 private:
  E m_val;
};

#ifdef __cpp_deduction_guides
template <class E>
unexpected(E) -> unexpected<E>;
#endif

template <class E>
constexpr bool operator==(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() == rhs.value();
}
template <class E>
constexpr bool operator!=(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() != rhs.value();
}
template <class E>
constexpr bool operator<(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() < rhs.value();
}
template <class E>
constexpr bool operator<=(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() <= rhs.value();
}
template <class E>
constexpr bool operator>(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() > rhs.value();
}
template <class E>
constexpr bool operator>=(const unexpected<E>& lhs, const unexpected<E>& rhs) {
  return lhs.value() >= rhs.value();
}

template <class E>
unexpected<typename std::decay<E>::type> make_unexpected(E&& e) {
  return unexpected<typename std::decay<E>::type>(std::forward<E>(e));
}

struct unexpect_t {
  unexpect_t() = default;
};
static constexpr unexpect_t unexpect{};

namespace detail {
template <typename E>
[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E&& e) {
#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
  throw std::forward<E>(e);
#else
  (void)e;
#ifdef _MSC_VER
  __assume(0);
#else
  __builtin_unreachable();
#endif
#endif
}

#ifndef TL_TRAITS_MUTEX
#define TL_TRAITS_MUTEX
// C++14-style aliases for brevity
template <class T>
using remove_const_t = typename std::remove_const<T>::type;
template <class T>
using remove_reference_t = typename std::remove_reference<T>::type;
template <class T>
using decay_t = typename std::decay<T>::type;
template <bool E, class T = void>
using enable_if_t = typename std::enable_if<E, T>::type;
template <bool B, class T, class F>
using conditional_t = typename std::conditional<B, T, F>::type;

// std::conjunction from C++17
template <class...>
struct conjunction : std::true_type {};
template <class B>
struct conjunction<B> : B {};
template <class B, class... Bs>
struct conjunction<B, Bs...>
    : std::conditional<bool(B::value), conjunction<Bs...>, B>::type {};

#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L
#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
#endif

// In C++11 mode, there's an issue in libc++'s std::mem_fn
// which results in a hard-error when using it in a noexcept expression
// in some cases. This is a check to workaround the common failing case.
#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
template <class T>
struct is_pointer_to_non_const_member_func : std::false_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...)>
    : std::true_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &>
    : std::true_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &&>
    : std::true_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile>
    : std::true_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile&>
    : std::true_type {};
template <class T, class Ret, class... Args>
struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile&&>
    : std::true_type {};

template <class T>
struct is_const_or_const_ref : std::false_type {};
template <class T>
struct is_const_or_const_ref<T const&> : std::true_type {};
template <class T>
struct is_const_or_const_ref<T const> : std::true_type {};
#endif

// std::invoke from C++17
// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround
template <
    typename Fn, typename... Args,
#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
    typename = enable_if_t<!(is_pointer_to_non_const_member_func<Fn>::value &&
                             is_const_or_const_ref<Args...>::value)>,
#endif
    typename = enable_if_t<std::is_member_pointer<decay_t<Fn>>::value>, int = 0>
constexpr auto invoke(Fn&& f, Args&&... args) noexcept(
    noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
    -> decltype(std::mem_fn(f)(std::forward<Args>(args)...)) {
  return std::mem_fn(f)(std::forward<Args>(args)...);
}

template <typename Fn, typename... Args,
          typename = enable_if_t<!std::is_member_pointer<decay_t<Fn>>::value>>
constexpr auto invoke(Fn&& f, Args&&... args) noexcept(
    noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
    -> decltype(std::forward<Fn>(f)(std::forward<Args>(args)...)) {
  return std::forward<Fn>(f)(std::forward<Args>(args)...);
}

// std::invoke_result from C++17
template <class F, class, class... Us>
struct invoke_result_impl;

template <class F, class... Us>
struct invoke_result_impl<
    F,
    decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...), void()),
    Us...> {
  using type =
      decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...));
};

template <class F, class... Us>
using invoke_result = invoke_result_impl<F, void, Us...>;

template <class F, class... Us>
using invoke_result_t = typename invoke_result<F, Us...>::type;

#if defined(_MSC_VER) && _MSC_VER <= 1900
// TODO make a version which works with MSVC 2015
template <class T, class U = T>
struct is_swappable : std::true_type {};

template <class T, class U = T>
struct is_nothrow_swappable : std::true_type {};
#else
// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept
namespace swap_adl_tests {
// if swap ADL finds this then it would call std::swap otherwise (same
// signature)
struct tag {};

template <class T>
tag swap(T&, T&);
template <class T, std::size_t N>
tag swap(T (&a)[N], T (&b)[N]);

// helper functions to test if an unqualified swap is possible, and if it
// becomes std::swap
template <class, class>
std::false_type can_swap(...) noexcept(false);
template <class T, class U,
          class = decltype(swap(std::declval<T&>(), std::declval<U&>()))>
std::true_type can_swap(int) noexcept(noexcept(swap(std::declval<T&>(),
                                                    std::declval<U&>())));

template <class, class>
std::false_type uses_std(...);
template <class T, class U>
std::is_same<decltype(swap(std::declval<T&>(), std::declval<U&>())), tag>
uses_std(int);

template <class T>
struct is_std_swap_noexcept
    : std::integral_constant<bool,
                             std::is_nothrow_move_constructible<T>::value &&
                                 std::is_nothrow_move_assignable<T>::value> {};

template <class T, std::size_t N>
struct is_std_swap_noexcept<T[N]> : is_std_swap_noexcept<T> {};

template <class T, class U>
struct is_adl_swap_noexcept
    : std::integral_constant<bool, noexcept(can_swap<T, U>(0))> {};
}  // namespace swap_adl_tests

template <class T, class U = T>
struct is_swappable
    : std::integral_constant<
          bool,
          decltype(detail::swap_adl_tests::can_swap<T, U>(0))::value &&
              (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value ||
               (std::is_move_assignable<T>::value &&
                std::is_move_constructible<T>::value))> {};

template <class T, std::size_t N>
struct is_swappable<T[N], T[N]>
    : std::integral_constant<
          bool,
          decltype(detail::swap_adl_tests::can_swap<T[N], T[N]>(0))::value &&
              (!decltype(detail::swap_adl_tests::uses_std<T[N], T[N]>(
                   0))::value ||
               is_swappable<T, T>::value)> {};

template <class T, class U = T>
struct is_nothrow_swappable
    : std::integral_constant<
          bool,
          is_swappable<T, U>::value &&
              ((decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
                detail::swap_adl_tests::is_std_swap_noexcept<T>::value) ||
               (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
                detail::swap_adl_tests::is_adl_swap_noexcept<T, U>::value))> {};
#endif
#endif

// Trait for checking if a type is a tl::expected
template <class T>
struct is_expected_impl : std::false_type {};
template <class T, class E>
struct is_expected_impl<expected<T, E>> : std::true_type {};
template <class T>
using is_expected = is_expected_impl<decay_t<T>>;

template <class T, class E, class U>
using expected_enable_forward_value = detail::enable_if_t<
    std::is_constructible<T, U&&>::value &&
    !std::is_same<detail::decay_t<U>, in_place_t>::value &&
    !std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
    !std::is_same<unexpected<E>, detail::decay_t<U>>::value>;

template <class T, class E, class U, class G, class UR, class GR>
using expected_enable_from_other = detail::enable_if_t<
    std::is_constructible<T, UR>::value &&
    std::is_constructible<E, GR>::value &&
    !std::is_constructible<T, expected<U, G>&>::value &&
    !std::is_constructible<T, expected<U, G>&&>::value &&
    !std::is_constructible<T, const expected<U, G>&>::value &&
    !std::is_constructible<T, const expected<U, G>&&>::value &&
    !std::is_convertible<expected<U, G>&, T>::value &&
    !std::is_convertible<expected<U, G>&&, T>::value &&
    !std::is_convertible<const expected<U, G>&, T>::value &&
    !std::is_convertible<const expected<U, G>&&, T>::value>;

template <class T, class U>
using is_void_or = conditional_t<std::is_void<T>::value, std::true_type, U>;

template <class T>
using is_copy_constructible_or_void =
    is_void_or<T, std::is_copy_constructible<T>>;

template <class T>
using is_move_constructible_or_void =
    is_void_or<T, std::is_move_constructible<T>>;

template <class T>
using is_copy_assignable_or_void = is_void_or<T, std::is_copy_assignable<T>>;

template <class T>
using is_move_assignable_or_void = is_void_or<T, std::is_move_assignable<T>>;

}  // namespace detail

namespace detail {
struct no_init_t {};
static constexpr no_init_t no_init{};

// Implements the storage of the values, and ensures that the destructor is
// trivial if it can be.
//
// This specialization is for where neither `T` or `E` is trivially
// destructible, so the destructors must be called on destruction of the
// `expected`
template <class T, class E, bool = std::is_trivially_destructible<T>::value,
          bool = std::is_trivially_destructible<E>::value>
struct expected_storage_base {
  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<T, Args&&...>::value>* =
                nullptr>
  constexpr expected_storage_base(in_place_t, Args&&... args)
      : m_val(std::forward<Args>(args)...), m_has_val(true) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
                                  Args&&... args)
      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() {
    if (m_has_val) {
      m_val.~T();
    } else {
      m_unexpect.~unexpected<E>();
    }
  }
  union {
    T m_val;
    unexpected<E> m_unexpect;
    char m_no_init;
  };
  bool m_has_val;
};

// This specialization is for when both `T` and `E` are trivially-destructible,
// so the destructor of the `expected` can be trivial.
template <class T, class E>
struct expected_storage_base<T, E, true, true> {
  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<T, Args&&...>::value>* =
                nullptr>
  constexpr expected_storage_base(in_place_t, Args&&... args)
      : m_val(std::forward<Args>(args)...), m_has_val(true) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
                                  Args&&... args)
      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() = default;
  union {
    T m_val;
    unexpected<E> m_unexpect;
    char m_no_init;
  };
  bool m_has_val;
};

// T is trivial, E is not.
template <class T, class E>
struct expected_storage_base<T, E, true, false> {
  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
  TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t)
      : m_no_init(), m_has_val(false) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<T, Args&&...>::value>* =
                nullptr>
  constexpr expected_storage_base(in_place_t, Args&&... args)
      : m_val(std::forward<Args>(args)...), m_has_val(true) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
                                  Args&&... args)
      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() {
    if (!m_has_val) {
      m_unexpect.~unexpected<E>();
    }
  }

  union {
    T m_val;
    unexpected<E> m_unexpect;
    char m_no_init;
  };
  bool m_has_val;
};

// E is trivial, T is not.
template <class T, class E>
struct expected_storage_base<T, E, false, true> {
  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<T, Args&&...>::value>* =
                nullptr>
  constexpr expected_storage_base(in_place_t, Args&&... args)
      : m_val(std::forward<Args>(args)...), m_has_val(true) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
                                  Args&&... args)
      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() {
    if (m_has_val) {
      m_val.~T();
    }
  }
  union {
    T m_val;
    unexpected<E> m_unexpect;
    char m_no_init;
  };
  bool m_has_val;
};

// `T` is `void`, `E` is trivially-destructible
template <class E>
struct expected_storage_base<void, E, false, true> {
#if __GNUC__ <= 5
// no constexpr for GCC 4/5 bug
#else
  TL_EXPECTED_MSVC2015_CONSTEXPR
#endif
  expected_storage_base() : m_has_val(true) {}

  constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {}

  constexpr expected_storage_base(in_place_t) : m_has_val(true) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() = default;
  struct dummy {};
  union {
    unexpected<E> m_unexpect;
    dummy m_val;
  };
  bool m_has_val;
};

// `T` is `void`, `E` is not trivially-destructible
template <class E>
struct expected_storage_base<void, E, false, false> {
  constexpr expected_storage_base() : m_dummy(), m_has_val(true) {}
  constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {}

  constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected_storage_base(unexpect_t, Args&&... args)
      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected_storage_base(unexpect_t,
                                           std::initializer_list<U> il,
                                           Args&&... args)
      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}

  ~expected_storage_base() {
    if (!m_has_val) {
      m_unexpect.~unexpected<E>();
    }
  }

  union {
    unexpected<E> m_unexpect;
    char m_dummy;
  };
  bool m_has_val;
};

// This base class provides some handy member functions which can be used in
// further derived classes
template <class T, class E>
struct expected_operations_base : expected_storage_base<T, E> {
  using expected_storage_base<T, E>::expected_storage_base;

  template <class... Args>
  void construct(Args&&... args) noexcept {
    new (std::addressof(this->m_val)) T(std::forward<Args>(args)...);
    this->m_has_val = true;
  }

  template <class Rhs>
  // NOLINTNEXTLINE(bugprone-exception-escape)
  void construct_with(Rhs&& rhs) noexcept {
    new (std::addressof(this->m_val)) T(std::forward<Rhs>(rhs).get());
    this->m_has_val = true;
  }

  template <class... Args>
  void construct_error(Args&&... args) noexcept {
    new (std::addressof(this->m_unexpect))
        unexpected<E>(std::forward<Args>(args)...);
    this->m_has_val = false;
  }

#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED

  // These assign overloads ensure that the most efficient assignment
  // implementation is used while maintaining the strong exception guarantee.
  // The problematic case is where rhs has a value, but *this does not.
  //
  // This overload handles the case where we can just copy-construct `T`
  // directly into place without throwing.
  template <class U = T,
            detail::enable_if_t<std::is_nothrow_copy_constructible<U>::value>* =
                nullptr>
  void assign(const expected_operations_base& rhs) noexcept {
    if (!this->m_has_val && rhs.m_has_val) {
      geterr().~unexpected<E>();
      construct(rhs.get());
    } else {
      assign_common(rhs);
    }
  }

  // This overload handles the case where we can attempt to create a copy of
  // `T`, then no-throw move it into place if the copy was successful.
  template <class U = T,
            detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&
                                std::is_nothrow_move_constructible<U>::value>* =
                nullptr>
  void assign(const expected_operations_base& rhs) noexcept {
    if (!this->m_has_val && rhs.m_has_val) {
      T tmp = rhs.get();
      geterr().~unexpected<E>();
      construct(std::move(tmp));
    } else {
      assign_common(rhs);
    }
  }

  // This overload is the worst-case, where we have to move-construct the
  // unexpected value into temporary storage, then try to copy the T into place.
  // If the construction succeeds, then everything is fine, but if it throws,
  // then we move the old unexpected value back into place before rethrowing the
  // exception.
  template <class U = T,
            detail::enable_if_t<
                !std::is_nothrow_copy_constructible<U>::value &&
                !std::is_nothrow_move_constructible<U>::value>* = nullptr>
  void assign(const expected_operations_base& rhs) {
    if (!this->m_has_val && rhs.m_has_val) {
      auto tmp = std::move(geterr());
      geterr().~unexpected<E>();

#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
      try {
        construct(rhs.get());
      } catch (...) {
        geterr() = std::move(tmp);
        throw;
      }
#else
      construct(rhs.get());
#endif
    } else {
      assign_common(rhs);
    }
  }

  // These overloads do the same as above, but for rvalues
  template <class U = T,
            detail::enable_if_t<std::is_nothrow_move_constructible<U>::value>* =
                nullptr>
  void assign(expected_operations_base&& rhs) noexcept {
    if (!this->m_has_val && rhs.m_has_val) {
      geterr().~unexpected<E>();
      construct(std::move(rhs).get());
    } else {
      assign_common(std::move(rhs));
    }
  }

  template <class U = T,
            detail::enable_if_t<
                !std::is_nothrow_move_constructible<U>::value>* = nullptr>
  void assign(expected_operations_base&& rhs) {
    if (!this->m_has_val && rhs.m_has_val) {
      auto tmp = std::move(geterr());
      geterr().~unexpected<E>();
#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
      try {
        construct(std::move(rhs).get());
      } catch (...) {
        geterr() = std::move(tmp);
        throw;
      }
#else
      construct(std::move(rhs).get());
#endif
    } else {
      assign_common(std::move(rhs));
    }
  }

#else

  // If exceptions are disabled then we can just copy-construct
  void assign(const expected_operations_base& rhs) noexcept {
    if (!this->m_has_val && rhs.m_has_val) {
      geterr().~unexpected<E>();
      construct(rhs.get());
    } else {
      assign_common(rhs);
    }
  }

  void assign(expected_operations_base&& rhs) noexcept {
    if (!this->m_has_val && rhs.m_has_val) {
      geterr().~unexpected<E>();
      construct(std::move(rhs).get());
    } else {
      assign_common(std::move(rhs));
    }
  }

#endif

  // The common part of move/copy assigning
  template <class Rhs>
  void assign_common(Rhs&& rhs) {
    if (this->m_has_val) {
      if (rhs.m_has_val) {
        get() = std::forward<Rhs>(rhs).get();
      } else {
        destroy_val();
        construct_error(std::forward<Rhs>(rhs).geterr());
      }
    } else {
      if (!rhs.m_has_val) {
        geterr() = std::forward<Rhs>(rhs).geterr();
      }
    }
  }

  bool has_value() const { return this->m_has_val; }

  TL_EXPECTED_11_CONSTEXPR T& get() & { return this->m_val; }
  constexpr const T& get() const& { return this->m_val; }
  TL_EXPECTED_11_CONSTEXPR T&& get() && { return std::move(this->m_val); }
#ifndef TL_EXPECTED_NO_CONSTRR
  constexpr const T&& get() const&& { return std::move(this->m_val); }
#endif

  TL_EXPECTED_11_CONSTEXPR unexpected<E>& geterr() & {
    return this->m_unexpect;
  }
  constexpr const unexpected<E>& geterr() const& { return this->m_unexpect; }
  TL_EXPECTED_11_CONSTEXPR unexpected<E>&& geterr() && {
    return std::move(this->m_unexpect);
  }
#ifndef TL_EXPECTED_NO_CONSTRR
  constexpr const unexpected<E>&& geterr() const&& {
    return std::move(this->m_unexpect);
  }
#endif

  TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); }
};

// This base class provides some handy member functions which can be used in
// further derived classes
template <class E>
struct expected_operations_base<void, E> : expected_storage_base<void, E> {
  using expected_storage_base<void, E>::expected_storage_base;

  template <class... Args>
  void construct() noexcept {
    this->m_has_val = true;
  }

  // This function doesn't use its argument, but needs it so that code in
  // levels above this can work independently of whether T is void
  template <class Rhs>
  void construct_with(Rhs&&) noexcept {
    this->m_has_val = true;
  }

  template <class... Args>
  void construct_error(Args&&... args) noexcept {
    new (std::addressof(this->m_unexpect))
        unexpected<E>(std::forward<Args>(args)...);
    this->m_has_val = false;
  }

  template <class Rhs>
  void assign(Rhs&& rhs) noexcept {
    if (!this->m_has_val) {
      if (rhs.m_has_val) {
        geterr().~unexpected<E>();
        construct();
      } else {
        geterr() = std::forward<Rhs>(rhs).geterr();
      }
    } else {
      if (!rhs.m_has_val) {
        construct_error(std::forward<Rhs>(rhs).geterr());
      }
    }
  }

  bool has_value() const { return this->m_has_val; }

  TL_EXPECTED_11_CONSTEXPR unexpected<E>& geterr() & {
    return this->m_unexpect;
  }
  constexpr const unexpected<E>& geterr() const& { return this->m_unexpect; }
  TL_EXPECTED_11_CONSTEXPR unexpected<E>&& geterr() && {
    return std::move(this->m_unexpect);
  }
#ifndef TL_EXPECTED_NO_CONSTRR
  constexpr const unexpected<E>&& geterr() const&& {
    return std::move(this->m_unexpect);
  }
#endif

  TL_EXPECTED_11_CONSTEXPR void destroy_val() {
    // no-op
  }
};

// This class manages conditionally having a trivial copy constructor
// This specialization is for when T and E are trivially copy constructible
template <class T, class E,
          bool = is_void_or<T, TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(
                                   T)>::value &&
                 TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value>
struct expected_copy_base : expected_operations_base<T, E> {
  using expected_operations_base<T, E>::expected_operations_base;
};

// This specialization is for when T or E are not trivially copy constructible
template <class T, class E>
struct expected_copy_base<T, E, false> : expected_operations_base<T, E> {
  using expected_operations_base<T, E>::expected_operations_base;

  expected_copy_base() = default;
  expected_copy_base(const expected_copy_base& rhs)
      : expected_operations_base<T, E>(no_init) {
    if (rhs.has_value()) {
      this->construct_with(rhs);
    } else {
      this->construct_error(rhs.geterr());
    }
  }

  expected_copy_base(expected_copy_base&& rhs) = default;
  expected_copy_base& operator=(const expected_copy_base& rhs) = default;
  expected_copy_base& operator=(expected_copy_base&& rhs) = default;
};

// This class manages conditionally having a trivial move constructor
// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it
// doesn't implement an analogue to std::is_trivially_move_constructible. We
// have to make do with a non-trivial move constructor even if T is trivially
// move constructible
#ifndef TL_EXPECTED_GCC49
template <class T, class E,
          bool =
              is_void_or<T, std::is_trivially_move_constructible<T>>::value &&
              std::is_trivially_move_constructible<E>::value>
struct expected_move_base : expected_copy_base<T, E> {
  using expected_copy_base<T, E>::expected_copy_base;
};
#else
template <class T, class E, bool = false>
struct expected_move_base;
#endif
template <class T, class E>
struct expected_move_base<T, E, false> : expected_copy_base<T, E> {
  using expected_copy_base<T, E>::expected_copy_base;

  expected_move_base() = default;
  expected_move_base(const expected_move_base& rhs) = default;

  expected_move_base(expected_move_base&& rhs) noexcept(
      std::is_nothrow_move_constructible<T>::value)
      : expected_copy_base<T, E>(no_init) {
    if (rhs.has_value()) {
      this->construct_with(std::move(rhs));
    } else {
      this->construct_error(std::move(rhs.geterr()));
    }
  }
  expected_move_base& operator=(const expected_move_base& rhs) = default;
  expected_move_base& operator=(expected_move_base&& rhs) = default;
};

// This class manages conditionally having a trivial copy assignment operator
template <
    class T, class E,
    bool =
        is_void_or<
            T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T),
                           TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T),
                           TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value &&
        TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value &&
        TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value &&
        TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value>
struct expected_copy_assign_base : expected_move_base<T, E> {
  using expected_move_base<T, E>::expected_move_base;
};

template <class T, class E>
struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {
  using expected_move_base<T, E>::expected_move_base;

  expected_copy_assign_base() = default;
  expected_copy_assign_base(const expected_copy_assign_base& rhs) = default;

  expected_copy_assign_base(expected_copy_assign_base&& rhs) = default;
  expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) {
    this->assign(rhs);
    return *this;
  }
  expected_copy_assign_base& operator=(expected_copy_assign_base&& rhs) =
      default;
};

// This class manages conditionally having a trivial move assignment operator
// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it
// doesn't implement an analogue to std::is_trivially_move_assignable. We have
// to make do with a non-trivial move assignment operator even if T is trivially
// move assignable
#ifndef TL_EXPECTED_GCC49
template <
    class T, class E,
    bool = is_void_or<
               T, conjunction<std::is_trivially_destructible<T>,
                              std::is_trivially_move_constructible<T>,
                              std::is_trivially_move_assignable<T>>>::value &&
           std::is_trivially_destructible<E>::value &&
           std::is_trivially_move_constructible<E>::value &&
           std::is_trivially_move_assignable<E>::value>
struct expected_move_assign_base : expected_copy_assign_base<T, E> {
  using expected_copy_assign_base<T, E>::expected_copy_assign_base;
};
#else
template <class T, class E, bool = false>
struct expected_move_assign_base;
#endif

template <class T, class E>
struct expected_move_assign_base<T, E, false>
    : expected_copy_assign_base<T, E> {
  using expected_copy_assign_base<T, E>::expected_copy_assign_base;

  expected_move_assign_base() = default;
  expected_move_assign_base(const expected_move_assign_base& rhs) = default;

  expected_move_assign_base(expected_move_assign_base&& rhs) = default;

  expected_move_assign_base& operator=(const expected_move_assign_base& rhs) =
      default;

  expected_move_assign_base&
  operator=(expected_move_assign_base&& rhs) noexcept(
      std::is_nothrow_move_constructible<T>::value &&
      std::is_nothrow_move_assignable<T>::value) {
    this->assign(std::move(rhs));
    return *this;
  }
};

// expected_delete_ctor_base will conditionally delete copy and move
// constructors depending on whether T is copy/move constructible
template <class T, class E,
          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&
                             std::is_copy_constructible<E>::value),
          bool EnableMove = (is_move_constructible_or_void<T>::value &&
                             std::is_move_constructible<E>::value)>
struct expected_delete_ctor_base {
  expected_delete_ctor_base() = default;
  expected_delete_ctor_base(const expected_delete_ctor_base&) = default;
  expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default;
  expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) =
      default;
  expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept =
      default;
};

template <class T, class E>
struct expected_delete_ctor_base<T, E, true, false> {
  expected_delete_ctor_base() = default;
  expected_delete_ctor_base(const expected_delete_ctor_base&) = default;
  expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete;
  expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) =
      default;
  expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept =
      default;
};

template <class T, class E>
struct expected_delete_ctor_base<T, E, false, true> {
  expected_delete_ctor_base() = default;
  expected_delete_ctor_base(const expected_delete_ctor_base&) = delete;
  expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default;
  expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) =
      default;
  expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept =
      default;
};

template <class T, class E>
struct expected_delete_ctor_base<T, E, false, false> {
  expected_delete_ctor_base() = default;
  expected_delete_ctor_base(const expected_delete_ctor_base&) = delete;
  expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete;
  expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) =
      default;
  expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept =
      default;
};

// expected_delete_assign_base will conditionally delete copy and move
// constructors depending on whether T and E are copy/move constructible +
// assignable
template <class T, class E,
          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&
                             std::is_copy_constructible<E>::value &&
                             is_copy_assignable_or_void<T>::value &&
                             std::is_copy_assignable<E>::value),
          bool EnableMove = (is_move_constructible_or_void<T>::value &&
                             std::is_move_constructible<E>::value &&
                             is_move_assignable_or_void<T>::value &&
                             std::is_move_assignable<E>::value)>
struct expected_delete_assign_base {
  expected_delete_assign_base() = default;
  expected_delete_assign_base(const expected_delete_assign_base&) = default;
  expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
  expected_delete_assign_base& operator=(const expected_delete_assign_base&) =
      default;
  expected_delete_assign_base& operator=(
      expected_delete_assign_base&&) noexcept = default;
};

template <class T, class E>
struct expected_delete_assign_base<T, E, true, false> {
  expected_delete_assign_base() = default;
  expected_delete_assign_base(const expected_delete_assign_base&) = default;
  expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
  expected_delete_assign_base& operator=(const expected_delete_assign_base&) =
      default;
  expected_delete_assign_base& operator=(
      expected_delete_assign_base&&) noexcept = delete;
};

template <class T, class E>
struct expected_delete_assign_base<T, E, false, true> {
  expected_delete_assign_base() = default;
  expected_delete_assign_base(const expected_delete_assign_base&) = default;
  expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
  expected_delete_assign_base& operator=(const expected_delete_assign_base&) =
      delete;
  expected_delete_assign_base& operator=(
      expected_delete_assign_base&&) noexcept = default;
};

template <class T, class E>
struct expected_delete_assign_base<T, E, false, false> {
  expected_delete_assign_base() = default;
  expected_delete_assign_base(const expected_delete_assign_base&) = default;
  expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default;
  expected_delete_assign_base& operator=(const expected_delete_assign_base&) =
      delete;
  expected_delete_assign_base& operator=(
      expected_delete_assign_base&&) noexcept = delete;
};

// This is needed to be able to construct the expected_default_ctor_base which
// follows, while still conditionally deleting the default constructor.
struct default_constructor_tag {
  explicit constexpr default_constructor_tag() = default;
};

// expected_default_ctor_base will ensure that expected has a deleted default
// consturctor if T is not default constructible.
// This specialization is for when T is default constructible
template <class T, class E,
          bool Enable =
              std::is_default_constructible<T>::value || std::is_void<T>::value>
struct expected_default_ctor_base {
  constexpr expected_default_ctor_base() noexcept = default;
  constexpr expected_default_ctor_base(
      expected_default_ctor_base const&) noexcept = default;
  constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept =
      default;
  expected_default_ctor_base& operator=(
      expected_default_ctor_base const&) noexcept = default;
  expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept =
      default;

  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
};

// This specialization is for when T is not default constructible
template <class T, class E>
struct expected_default_ctor_base<T, E, false> {
  constexpr expected_default_ctor_base() noexcept = delete;
  constexpr expected_default_ctor_base(
      expected_default_ctor_base const&) noexcept = default;
  constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept =
      default;
  expected_default_ctor_base& operator=(
      expected_default_ctor_base const&) noexcept = default;
  expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept =
      default;

  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
};
}  // namespace detail

template <class E>
class bad_expected_access : public std::exception {
 public:
  explicit bad_expected_access(E e) : m_val(std::move(e)) {}

  virtual const char* what() const noexcept override {
    return "Bad expected access";
  }

  const E& error() const& { return m_val; }
  E& error() & { return m_val; }
  const E&& error() const&& { return std::move(m_val); }
  E&& error() && { return std::move(m_val); }

 private:
  E m_val;
};

/// An `expected<T, E>` object is an object that contains the storage for
/// another object and manages the lifetime of this contained object `T`.
/// Alternatively it could contain the storage for another unexpected object
/// `E`. The contained object may not be initialized after the expected object
/// has been initialized, and may not be destroyed before the expected object
/// has been destroyed. The initialization state of the contained object is
/// tracked by the expected object.
template <class T, class E>
class expected : private detail::expected_move_assign_base<T, E>,
                 private detail::expected_delete_ctor_base<T, E>,
                 private detail::expected_delete_assign_base<T, E>,
                 private detail::expected_default_ctor_base<T, E> {
  static_assert(!std::is_reference<T>::value, "T must not be a reference");
  static_assert(!std::is_same<T, std::remove_cv<in_place_t>::type>::value,
                "T must not be in_place_t");
  static_assert(!std::is_same<T, std::remove_cv<unexpect_t>::type>::value,
                "T must not be unexpect_t");
  static_assert(
      !std::is_same<T, typename std::remove_cv<unexpected<E>>::type>::value,
      "T must not be unexpected<E>");
  static_assert(!std::is_reference<E>::value, "E must not be a reference");

  T* valptr() { return std::addressof(this->m_val); }
  const T* valptr() const { return std::addressof(this->m_val); }
  unexpected<E>* errptr() { return std::addressof(this->m_unexpect); }
  const unexpected<E>* errptr() const {
    return std::addressof(this->m_unexpect);
  }

  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR U& val() {
    return this->m_val;
  }
  TL_EXPECTED_11_CONSTEXPR unexpected<E>& err() { return this->m_unexpect; }

  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  constexpr const U& val() const {
    return this->m_val;
  }
  constexpr const unexpected<E>& err() const { return this->m_unexpect; }

  using impl_base = detail::expected_move_assign_base<T, E>;
  using ctor_base = detail::expected_default_ctor_base<T, E>;

 public:
  typedef T value_type;
  typedef E error_type;
  typedef unexpected<E> unexpected_type;

#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & {
    return and_then_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && {
    return and_then_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto and_then(F&& f) const& {
    return and_then_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr auto and_then(F&& f) const&& {
    return and_then_impl(std::move(*this), std::forward<F>(f));
  }
#endif

#else
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & -> decltype(and_then_impl(
      std::declval<expected&>(), std::forward<F>(f))) {
    return and_then_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && -> decltype(and_then_impl(
      std::declval<expected&&>(), std::forward<F>(f))) {
    return and_then_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto and_then(F&& f) const& -> decltype(and_then_impl(
      std::declval<expected const&>(), std::forward<F>(f))) {
    return and_then_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr auto and_then(F&& f) const&& -> decltype(and_then_impl(
      std::declval<expected const&&>(), std::forward<F>(f))) {
    return and_then_impl(std::move(*this), std::forward<F>(f));
  }
#endif
#endif

#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto map(F&& f) & {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto map(F&& f) && {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto map(F&& f) const& {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  constexpr auto map(F&& f) const&& {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
#else
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(
      std::declval<expected&>(), std::declval<F&&>())) map(F&& f) & {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),
                                                      std::declval<F&&>()))
  map(F&& f) && {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr decltype(expected_map_impl(std::declval<const expected&>(),
                                       std::declval<F&&>()))
  map(F&& f) const& {
    return expected_map_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr decltype(expected_map_impl(std::declval<const expected&&>(),
                                       std::declval<F&&>())) map(F&& f)
      const&& {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
#endif
#endif

#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) & {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) && {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto transform(F&& f) const& {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  constexpr auto transform(F&& f) const&& {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
#else
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(
      std::declval<expected&>(), std::declval<F&&>())) transform(F&& f) & {
    return expected_map_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),
                                                      std::declval<F&&>()))
  transform(F&& f) && {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr decltype(expected_map_impl(std::declval<const expected&>(),
                                       std::declval<F&&>()))
  transform(F&& f) const& {
    return expected_map_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr decltype(expected_map_impl(std::declval<const expected&&>(),
                                       std::declval<F&&>())) transform(F&& f)
      const&& {
    return expected_map_impl(std::move(*this), std::forward<F>(f));
  }
#endif
#endif

#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) & {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) && {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto map_error(F&& f) const& {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  constexpr auto map_error(F&& f) const&& {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
#else
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(
      std::declval<expected&>(), std::declval<F&&>())) map_error(F&& f) & {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected&&>(),
                                                   std::declval<F&&>()))
  map_error(F&& f) && {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr decltype(map_error_impl(std::declval<const expected&>(),
                                    std::declval<F&&>()))
  map_error(F&& f) const& {
    return map_error_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr decltype(map_error_impl(std::declval<const expected&&>(),
                                    std::declval<F&&>())) map_error(F&& f)
      const&& {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
#endif
#endif
#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) & {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) && {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr auto transform_error(F&& f) const& {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  constexpr auto transform_error(F&& f) const&& {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
#else
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(
      std::declval<expected&>(),
      std::declval<F&&>())) transform_error(F&& f) & {
    return map_error_impl(*this, std::forward<F>(f));
  }
  template <class F>
  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected&&>(),
                                                   std::declval<F&&>()))
  transform_error(F&& f) && {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
  template <class F>
  constexpr decltype(map_error_impl(std::declval<const expected&>(),
                                    std::declval<F&&>()))
  transform_error(F&& f) const& {
    return map_error_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  constexpr decltype(map_error_impl(std::declval<const expected&&>(),
                                    std::declval<F&&>())) transform_error(F&& f)
      const&& {
    return map_error_impl(std::move(*this), std::forward<F>(f));
  }
#endif
#endif
  template <class F>
  expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) & {
    return or_else_impl(*this, std::forward<F>(f));
  }

  template <class F>
  expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) && {
    return or_else_impl(std::move(*this), std::forward<F>(f));
  }

  template <class F>
  expected constexpr or_else(F&& f) const& {
    return or_else_impl(*this, std::forward<F>(f));
  }

#ifndef TL_EXPECTED_NO_CONSTRR
  template <class F>
  expected constexpr or_else(F&& f) const&& {
    return or_else_impl(std::move(*this), std::forward<F>(f));
  }
#endif
  constexpr expected() = default;
  constexpr expected(const expected& rhs) = default;
  constexpr expected(expected&& rhs) = default;
  expected& operator=(const expected& rhs) = default;
  expected& operator=(expected&& rhs) = default;

  template <class... Args,
            detail::enable_if_t<std::is_constructible<T, Args&&...>::value>* =
                nullptr>
  constexpr expected(in_place_t, Args&&... args)
      : impl_base(in_place, std::forward<Args>(args)...),
        ctor_base(detail::default_constructor_tag{}) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr expected(in_place_t, std::initializer_list<U> il, Args&&... args)
      : impl_base(in_place, il, std::forward<Args>(args)...),
        ctor_base(detail::default_constructor_tag{}) {}

  template <
      class G = E,
      detail::enable_if_t<std::is_constructible<E, const G&>::value>* = nullptr,
      detail::enable_if_t<!std::is_convertible<const G&, E>::value>* = nullptr>
  explicit constexpr expected(const unexpected<G>& e)
      : impl_base(unexpect, e.value()),
        ctor_base(detail::default_constructor_tag{}) {}

  template <
      class G = E,
      detail::enable_if_t<std::is_constructible<E, const G&>::value>* = nullptr,
      detail::enable_if_t<std::is_convertible<const G&, E>::value>* = nullptr>
  constexpr expected(unexpected<G> const& e)
      : impl_base(unexpect, e.value()),
        ctor_base(detail::default_constructor_tag{}) {}

  template <
      class G = E,
      detail::enable_if_t<std::is_constructible<E, G&&>::value>* = nullptr,
      detail::enable_if_t<!std::is_convertible<G&&, E>::value>* = nullptr>
  explicit constexpr expected(unexpected<G>&& e) noexcept(
      std::is_nothrow_constructible<E, G&&>::value)
      : impl_base(unexpect, std::move(e.value())),
        ctor_base(detail::default_constructor_tag{}) {}

  template <
      class G = E,
      detail::enable_if_t<std::is_constructible<E, G&&>::value>* = nullptr,
      detail::enable_if_t<std::is_convertible<G&&, E>::value>* = nullptr>
  constexpr expected(unexpected<G>&& e) noexcept(
      std::is_nothrow_constructible<E, G&&>::value)
      : impl_base(unexpect, std::move(e.value())),
        ctor_base(detail::default_constructor_tag{}) {}

  template <class... Args,
            detail::enable_if_t<std::is_constructible<E, Args&&...>::value>* =
                nullptr>
  constexpr explicit expected(unexpect_t, Args&&... args)
      : impl_base(unexpect, std::forward<Args>(args)...),
        ctor_base(detail::default_constructor_tag{}) {}

  template <class U, class... Args,
            detail::enable_if_t<std::is_constructible<
                E, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  constexpr explicit expected(unexpect_t, std::initializer_list<U> il,
                              Args&&... args)
      : impl_base(unexpect, il, std::forward<Args>(args)...),
        ctor_base(detail::default_constructor_tag{}) {}

  template <class U, class G,
            detail::enable_if_t<!(std::is_convertible<U const&, T>::value &&
                                  std::is_convertible<G const&, E>::value)>* =
                nullptr,
            detail::expected_enable_from_other<T, E, U, G, const U&,
                                               const G&>* = nullptr>
  explicit TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G>& rhs)
      : ctor_base(detail::default_constructor_tag{}) {
    if (rhs.has_value()) {
      this->construct(*rhs);
    } else {
      this->construct_error(rhs.error());
    }
  }

  template <
      class U, class G,
      detail::enable_if_t<(std::is_convertible<U const&, T>::value &&
                           std::is_convertible<G const&, E>::value)>* = nullptr,
      detail::expected_enable_from_other<T, E, U, G, const U&, const G&>* =
          nullptr>
  TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G>& rhs)
      : ctor_base(detail::default_constructor_tag{}) {
    if (rhs.has_value()) {
      this->construct(*rhs);
    } else {
      this->construct_error(rhs.error());
    }
  }

  template <
      class U, class G,
      detail::enable_if_t<!(std::is_convertible<U&&, T>::value &&
                            std::is_convertible<G&&, E>::value)>* = nullptr,
      detail::expected_enable_from_other<T, E, U, G, U&&, G&&>* = nullptr>
  explicit TL_EXPECTED_11_CONSTEXPR expected(expected<U, G>&& rhs)
      : ctor_base(detail::default_constructor_tag{}) {
    if (rhs.has_value()) {
      this->construct(std::move(*rhs));
    } else {
      this->construct_error(std::move(rhs.error()));
    }
  }

  template <
      class U, class G,
      detail::enable_if_t<(std::is_convertible<U&&, T>::value &&
                           std::is_convertible<G&&, E>::value)>* = nullptr,
      detail::expected_enable_from_other<T, E, U, G, U&&, G&&>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR expected(expected<U, G>&& rhs)
      : ctor_base(detail::default_constructor_tag{}) {
    if (rhs.has_value()) {
      this->construct(std::move(*rhs));
    } else {
      this->construct_error(std::move(rhs.error()));
    }
  }

  template <class U = T,
            detail::enable_if_t<!std::is_convertible<U&&, T>::value>* = nullptr,
            detail::expected_enable_forward_value<T, E, U>* = nullptr>
  explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v)
      : expected(in_place, std::forward<U>(v)) {}

  template <class U = T,
            detail::enable_if_t<std::is_convertible<U&&, T>::value>* = nullptr,
            detail::expected_enable_forward_value<T, E, U>* = nullptr>
  TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v)
      : expected(in_place, std::forward<U>(v)) {}

  template <
      class U = T, class G = T,
      detail::enable_if_t<std::is_nothrow_constructible<T, U&&>::value>* =
          nullptr,
      detail::enable_if_t<!std::is_void<G>::value>* = nullptr,
      detail::enable_if_t<
          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
           !detail::conjunction<std::is_scalar<T>,
                                std::is_same<T, detail::decay_t<U>>>::value &&
           std::is_constructible<T, U>::value &&
           std::is_assignable<G&, U>::value &&
           std::is_nothrow_move_constructible<E>::value)>* = nullptr>
  expected& operator=(U&& v) {
    if (has_value()) {
      val() = std::forward<U>(v);
    } else {
      err().~unexpected<E>();
      ::new (valptr()) T(std::forward<U>(v));
      this->m_has_val = true;
    }

    return *this;
  }

  template <
      class U = T, class G = T,
      detail::enable_if_t<!std::is_nothrow_constructible<T, U&&>::value>* =
          nullptr,
      detail::enable_if_t<!std::is_void<U>::value>* = nullptr,
      detail::enable_if_t<
          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
           !detail::conjunction<std::is_scalar<T>,
                                std::is_same<T, detail::decay_t<U>>>::value &&
           std::is_constructible<T, U>::value &&
           std::is_assignable<G&, U>::value &&
           std::is_nothrow_move_constructible<E>::value)>* = nullptr>
  expected& operator=(U&& v) {
    if (has_value()) {
      val() = std::forward<U>(v);
    } else {
      auto tmp = std::move(err());
      err().~unexpected<E>();

#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
      try {
        ::new (valptr()) T(std::forward<U>(v));
        this->m_has_val = true;
      } catch (...) {
        err() = std::move(tmp);
        throw;
      }
#else
      ::new (valptr()) T(std::forward<U>(v));
      this->m_has_val = true;
#endif
    }

    return *this;
  }

  template <class G = E,
            detail::enable_if_t<std::is_nothrow_copy_constructible<G>::value &&
                                std::is_assignable<G&, G>::value>* = nullptr>
  expected& operator=(const unexpected<G>& rhs) {
    if (!has_value()) {
      err() = rhs;
    } else {
      this->destroy_val();
      ::new (errptr()) unexpected<E>(rhs);
      this->m_has_val = false;
    }

    return *this;
  }

  template <class G = E,
            detail::enable_if_t<std::is_nothrow_move_constructible<G>::value &&
                                std::is_move_assignable<G>::value>* = nullptr>
  expected& operator=(unexpected<G>&& rhs) noexcept {
    if (!has_value()) {
      err() = std::move(rhs);
    } else {
      this->destroy_val();
      ::new (errptr()) unexpected<E>(std::move(rhs));
      this->m_has_val = false;
    }

    return *this;
  }

  template <class... Args, detail::enable_if_t<std::is_nothrow_constructible<
                               T, Args&&...>::value>* = nullptr>
  void emplace(Args&&... args) {
    if (has_value()) {
      val().~T();
    } else {
      err().~unexpected<E>();
      this->m_has_val = true;
    }
    ::new (valptr()) T(std::forward<Args>(args)...);
  }

  template <class... Args, detail::enable_if_t<!std::is_nothrow_constructible<
                               T, Args&&...>::value>* = nullptr>
  void emplace(Args&&... args) {
    if (has_value()) {
      val().~T();
      ::new (valptr()) T(std::forward<Args>(args)...);
    } else {
      auto tmp = std::move(err());
      err().~unexpected<E>();

#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
      try {
        ::new (valptr()) T(std::forward<Args>(args)...);
        this->m_has_val = true;
      } catch (...) {
        err() = std::move(tmp);
        throw;
      }
#else
      ::new (valptr()) T(std::forward<Args>(args)...);
      this->m_has_val = true;
#endif
    }
  }

  template <class U, class... Args,
            detail::enable_if_t<std::is_nothrow_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  void emplace(std::initializer_list<U> il, Args&&... args) {
    if (has_value()) {
      T t(il, std::forward<Args>(args)...);
      val() = std::move(t);
    } else {
      err().~unexpected<E>();
      ::new (valptr()) T(il, std::forward<Args>(args)...);
      this->m_has_val = true;
    }
  }

  template <class U, class... Args,
            detail::enable_if_t<!std::is_nothrow_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>* = nullptr>
  void emplace(std::initializer_list<U> il, Args&&... args) {
    if (has_value()) {
      T t(il, std::forward<Args>(args)...);
      val() = std::move(t);
    } else {
      auto tmp = std::move(err());
      err().~unexpected<E>();

#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
      try {
        ::new (valptr()) T(il, std::forward<Args>(args)...);
        this->m_has_val = true;
      } catch (...) {
        err() = std::move(tmp);
        throw;
      }
#else
      ::new (valptr()) T(il, std::forward<Args>(args)...);
      this->m_has_val = true;
#endif
    }
  }

 private:
  using t_is_void = std::true_type;
  using t_is_not_void = std::false_type;
  using t_is_nothrow_move_constructible = std::true_type;
  using move_constructing_t_can_throw = std::false_type;
  using e_is_nothrow_move_constructible = std::true_type;
  using move_constructing_e_can_throw = std::false_type;

  void swap_where_both_have_value(expected& /*rhs*/, t_is_void) noexcept {
    // swapping void is a no-op
  }

  void swap_where_both_have_value(expected& rhs, t_is_not_void) {
    using std::swap;
    swap(val(), rhs.val());
  }

  void swap_where_only_one_has_value(expected& rhs, t_is_void) noexcept(
      std::is_nothrow_move_constructible<E>::value) {
    ::new (errptr()) unexpected_type(std::move(rhs.err()));
    rhs.err().~unexpected_type();
    std::swap(this->m_has_val, rhs.m_has_val);
  }

  void swap_where_only_one_has_value(expected& rhs, t_is_not_void) {
    swap_where_only_one_has_value_and_t_is_not_void(
        rhs, typename std::is_nothrow_move_constructible<T>::type{},
        typename std::is_nothrow_move_constructible<E>::type{});
  }

  void swap_where_only_one_has_value_and_t_is_not_void(
      expected& rhs, t_is_nothrow_move_constructible,
      e_is_nothrow_move_constructible) noexcept {
    auto temp = std::move(val());
    val().~T();
    ::new (errptr()) unexpected_type(std::move(rhs.err()));
    rhs.err().~unexpected_type();
    ::new (rhs.valptr()) T(std::move(temp));
    std::swap(this->m_has_val, rhs.m_has_val);
  }

  void swap_where_only_one_has_value_and_t_is_not_void(
      expected& rhs, t_is_nothrow_move_constructible,
      move_constructing_e_can_throw) {
    auto temp = std::move(val());
    val().~T();
#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
    try {
      ::new (errptr()) unexpected_type(std::move(rhs.err()));
      rhs.err().~unexpected_type();
      ::new (rhs.valptr()) T(std::move(temp));
      std::swap(this->m_has_val, rhs.m_has_val);
    } catch (...) {
      val() = std::move(temp);
      throw;
    }
#else
    ::new (errptr()) unexpected_type(std::move(rhs.err()));
    rhs.err().~unexpected_type();
    ::new (rhs.valptr()) T(std::move(temp));
    std::swap(this->m_has_val, rhs.m_has_val);
#endif
  }

  void swap_where_only_one_has_value_and_t_is_not_void(
      expected& rhs, move_constructing_t_can_throw,
      e_is_nothrow_move_constructible) {
    auto temp = std::move(rhs.err());
    rhs.err().~unexpected_type();
#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
    try {
      ::new (rhs.valptr()) T(std::move(val()));
      val().~T();
      ::new (errptr()) unexpected_type(std::move(temp));
      std::swap(this->m_has_val, rhs.m_has_val);
    } catch (...) {
      rhs.err() = std::move(temp);
      throw;
    }
#else
    ::new (rhs.valptr()) T(std::move(val()));
    val().~T();
    ::new (errptr()) unexpected_type(std::move(temp));
    std::swap(this->m_has_val, rhs.m_has_val);
#endif
  }

 public:
  template <class OT = T, class OE = E>
  detail::enable_if_t<detail::is_swappable<OT>::value &&
                      detail::is_swappable<OE>::value &&
                      (std::is_nothrow_move_constructible<OT>::value ||
                       std::is_nothrow_move_constructible<OE>::value)>
  swap(expected& rhs) noexcept(std::is_nothrow_move_constructible<T>::value &&
                               detail::is_nothrow_swappable<T>::value &&
                               std::is_nothrow_move_constructible<E>::value &&
                               detail::is_nothrow_swappable<E>::value) {
    if (has_value() && rhs.has_value()) {
      swap_where_both_have_value(rhs, typename std::is_void<T>::type{});
    } else if (!has_value() && rhs.has_value()) {
      rhs.swap(*this);
    } else if (has_value()) {
      swap_where_only_one_has_value(rhs, typename std::is_void<T>::type{});
    } else {
      using std::swap;
      swap(err(), rhs.err());
    }
  }

  constexpr const T* operator->() const {
    TL_ASSERT(has_value());
    return valptr();
  }
  TL_EXPECTED_11_CONSTEXPR T* operator->() {
    TL_ASSERT(has_value());
    return valptr();
  }

  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  constexpr const U& operator*() const& {
    TL_ASSERT(has_value());
    return val();
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR U& operator*() & {
    TL_ASSERT(has_value());
    return val();
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  constexpr const U&& operator*() const&& {
    TL_ASSERT(has_value());
    return std::move(val());
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR U&& operator*() && {
    TL_ASSERT(has_value());
    return std::move(val());
  }

  constexpr bool has_value() const noexcept { return this->m_has_val; }
  constexpr explicit operator bool() const noexcept { return this->m_has_val; }

  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR const U& value() const& {
    if (!has_value())
      detail::throw_exception(bad_expected_access<E>(err().value()));
    return val();
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR U& value() & {
    if (!has_value())
      detail::throw_exception(bad_expected_access<E>(err().value()));
    return val();
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR const U&& value() const&& {
    if (!has_value())
      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));
    return std::move(val());
  }
  template <class U = T,
            detail::enable_if_t<!std::is_void<U>::value>* = nullptr>
  TL_EXPECTED_11_CONSTEXPR U&& value() && {
    if (!has_value())
      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));
    return std::move(val());
  }

  constexpr const E& error() const& {
    TL_ASSERT(!has_value());
    return err().value();
  }
  TL_EXPECTED_11_CONSTEXPR E& error() & {
    TL_ASSERT(!has_value());
    return err().value();
  }
  constexpr const E&& error() const&& {
    TL_ASSERT(!has_value());
    return std::move(err().value());
  }
  TL_EXPECTED_11_CONSTEXPR E&& error() && {
    TL_ASSERT(!has_value());
    return std::move(err().value());
  }

  template <class U>
  constexpr T value_or(U&& v) const& {
    static_assert(std::is_copy_constructible<T>::value &&
                      std::is_convertible<U&&, T>::value,
                  "T must be copy-constructible and convertible to from U&&");
    return bool(*this) ? **this : static_cast<T>(std::forward<U>(v));
  }
  template <class U>
  TL_EXPECTED_11_CONSTEXPR T value_or(U&& v) && {
    static_assert(std::is_move_constructible<T>::value &&
                      std::is_convertible<U&&, T>::value,
                  "T must be move-constructible and convertible to from U&&");
    return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v));
  }
};

namespace detail {
template <class Exp>
using exp_t = typename detail::decay_t<Exp>::value_type;
template <class Exp>
using err_t = typename detail::decay_t<Exp>::error_type;
template <class Exp, class Ret>
using ret_t = expected<Ret, err_t<Exp>>;

#ifdef TL_EXPECTED_CXX14
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>()))>
constexpr auto and_then_impl(Exp&& exp, F&& f) {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");

  return exp.has_value()
             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
             : Ret(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>()))>
constexpr auto and_then_impl(Exp&& exp, F&& f) {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");

  return exp.has_value() ? detail::invoke(std::forward<F>(f))
                         : Ret(unexpect, std::forward<Exp>(exp).error());
}
#else
template <class>
struct TC;
template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>())),
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr>
auto and_then_impl(Exp&& exp, F&& f) -> Ret {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");

  return exp.has_value()
             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
             : Ret(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>())),
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr>
constexpr auto and_then_impl(Exp&& exp, F&& f) -> Ret {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");

  return exp.has_value() ? detail::invoke(std::forward<F>(f))
                         : Ret(unexpect, std::forward<Exp>(exp).error());
}
#endif

#ifdef TL_EXPECTED_CXX14
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto expected_map_impl(Exp&& exp, F&& f) {
  using result = ret_t<Exp, detail::decay_t<Ret>>;
  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
                                                 *std::forward<Exp>(exp)))
                         : result(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto expected_map_impl(Exp&& exp, F&& f) {
  using result = expected<void, err_t<Exp>>;
  if (exp.has_value()) {
    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
    return result();
  }

  return result(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto expected_map_impl(Exp&& exp, F&& f) {
  using result = ret_t<Exp, detail::decay_t<Ret>>;
  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))
                         : result(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto expected_map_impl(Exp&& exp, F&& f) {
  using result = expected<void, err_t<Exp>>;
  if (exp.has_value()) {
    detail::invoke(std::forward<F>(f));
    return result();
  }

  return result(unexpect, std::forward<Exp>(exp).error());
}
#else
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>

constexpr auto expected_map_impl(Exp&& exp, F&& f)
    -> ret_t<Exp, detail::decay_t<Ret>> {
  using result = ret_t<Exp, detail::decay_t<Ret>>;

  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
                                                 *std::forward<Exp>(exp)))
                         : result(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              *std::declval<Exp>())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>

auto expected_map_impl(Exp&& exp, F&& f) -> expected<void, err_t<Exp>> {
  if (exp.has_value()) {
    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
    return {};
  }

  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>

constexpr auto expected_map_impl(Exp&& exp, F&& f)
    -> ret_t<Exp, detail::decay_t<Ret>> {
  using result = ret_t<Exp, detail::decay_t<Ret>>;

  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))
                         : result(unexpect, std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>

auto expected_map_impl(Exp&& exp, F&& f) -> expected<void, err_t<Exp>> {
  if (exp.has_value()) {
    detail::invoke(std::forward<F>(f));
    return {};
  }

  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());
}
#endif

#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto map_error_impl(Exp&& exp, F&& f) {
  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
  return exp.has_value()
             ? result(*std::forward<Exp>(exp))
             : result(unexpect, detail::invoke(std::forward<F>(f),
                                               std::forward<Exp>(exp).error()));
}
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto map_error_impl(Exp&& exp, F&& f) {
  using result = expected<exp_t<Exp>, monostate>;
  if (exp.has_value()) {
    return result(*std::forward<Exp>(exp));
  }

  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
  return result(unexpect, monostate{});
}
template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto map_error_impl(Exp&& exp, F&& f) {
  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
  return exp.has_value()
             ? result()
             : result(unexpect, detail::invoke(std::forward<F>(f),
                                               std::forward<Exp>(exp).error()));
}
template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto map_error_impl(Exp&& exp, F&& f) {
  using result = expected<exp_t<Exp>, monostate>;
  if (exp.has_value()) {
    return result();
  }

  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
  return result(unexpect, monostate{});
}
#else
template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto map_error_impl(Exp&& exp, F&& f)
    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {
  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;

  return exp.has_value()
             ? result(*std::forward<Exp>(exp))
             : result(unexpect, detail::invoke(std::forward<F>(f),
                                               std::forward<Exp>(exp).error()));
}

template <class Exp, class F,
          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto map_error_impl(Exp&& exp, F&& f) -> expected<exp_t<Exp>, monostate> {
  using result = expected<exp_t<Exp>, monostate>;
  if (exp.has_value()) {
    return result(*std::forward<Exp>(exp));
  }

  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
  return result(unexpect, monostate{});
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto map_error_impl(Exp&& exp, F&& f)
    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {
  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;

  return exp.has_value()
             ? result()
             : result(unexpect, detail::invoke(std::forward<F>(f),
                                               std::forward<Exp>(exp).error()));
}

template <class Exp, class F,
          detail::enable_if_t<std::is_void<exp_t<Exp>>::value>* = nullptr,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
auto map_error_impl(Exp&& exp, F&& f) -> expected<exp_t<Exp>, monostate> {
  using result = expected<exp_t<Exp>, monostate>;
  if (exp.has_value()) {
    return result();
  }

  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
  return result(unexpect, monostate{});
}
#endif

#ifdef TL_EXPECTED_CXX14
template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
constexpr auto or_else_impl(Exp&& exp, F&& f) {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
  return exp.has_value() ? std::forward<Exp>(exp)
                         : detail::invoke(std::forward<F>(f),
                                          std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
detail::decay_t<Exp> or_else_impl(Exp&& exp, F&& f) {
  return exp.has_value() ? std::forward<Exp>(exp)
                         : (detail::invoke(std::forward<F>(f),
                                           std::forward<Exp>(exp).error()),
                            std::forward<Exp>(exp));
}
#else
template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<!std::is_void<Ret>::value>* = nullptr>
auto or_else_impl(Exp&& exp, F&& f) -> Ret {
  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
  return exp.has_value() ? std::forward<Exp>(exp)
                         : detail::invoke(std::forward<F>(f),
                                          std::forward<Exp>(exp).error());
}

template <class Exp, class F,
          class Ret = decltype(detail::invoke(std::declval<F>(),
                                              std::declval<Exp>().error())),
          detail::enable_if_t<std::is_void<Ret>::value>* = nullptr>
detail::decay_t<Exp> or_else_impl(Exp&& exp, F&& f) {
  return exp.has_value() ? std::forward<Exp>(exp)
                         : (detail::invoke(std::forward<F>(f),
                                           std::forward<Exp>(exp).error()),
                            std::forward<Exp>(exp));
}
#endif
}  // namespace detail

template <class T, class E, class U, class F>
constexpr bool operator==(const expected<T, E>& lhs,
                          const expected<U, F>& rhs) {
  return (lhs.has_value() != rhs.has_value())
             ? false
             : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs);
}
template <class T, class E, class U, class F>
constexpr bool operator!=(const expected<T, E>& lhs,
                          const expected<U, F>& rhs) {
  return (lhs.has_value() != rhs.has_value())
             ? true
             : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs);
}
template <class E, class F>
constexpr bool operator==(const expected<void, E>& lhs,
                          const expected<void, F>& rhs) {
  return (lhs.has_value() != rhs.has_value())
             ? false
             : (!lhs.has_value() ? lhs.error() == rhs.error() : true);
}
template <class E, class F>
constexpr bool operator!=(const expected<void, E>& lhs,
                          const expected<void, F>& rhs) {
  return (lhs.has_value() != rhs.has_value())
             ? true
             : (!lhs.has_value() ? lhs.error() == rhs.error() : false);
}

template <class T, class E, class U>
constexpr bool operator==(const expected<T, E>& x, const U& v) {
  return x.has_value() ? *x == v : false;
}
template <class T, class E, class U>
constexpr bool operator==(const U& v, const expected<T, E>& x) {
  return x.has_value() ? *x == v : false;
}
template <class T, class E, class U>
constexpr bool operator!=(const expected<T, E>& x, const U& v) {
  return x.has_value() ? *x != v : true;
}
template <class T, class E, class U>
constexpr bool operator!=(const U& v, const expected<T, E>& x) {
  return x.has_value() ? *x != v : true;
}

template <class T, class E>
constexpr bool operator==(const expected<T, E>& x, const unexpected<E>& e) {
  return x.has_value() ? false : x.error() == e.value();
}
template <class T, class E>
constexpr bool operator==(const unexpected<E>& e, const expected<T, E>& x) {
  return x.has_value() ? false : x.error() == e.value();
}
template <class T, class E>
constexpr bool operator!=(const expected<T, E>& x, const unexpected<E>& e) {
  return x.has_value() ? true : x.error() != e.value();
}
template <class T, class E>
constexpr bool operator!=(const unexpected<E>& e, const expected<T, E>& x) {
  return x.has_value() ? true : x.error() != e.value();
}

template <class T, class E,
          detail::enable_if_t<(std::is_void<T>::value ||
                               std::is_move_constructible<T>::value) &&
                              detail::is_swappable<T>::value &&
                              std::is_move_constructible<E>::value &&
                              detail::is_swappable<E>::value>* = nullptr>
void swap(expected<T, E>& lhs,
          expected<T, E>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
  lhs.swap(rhs);
}
}  // namespace tl

#endif
/* end file include/ada/expected.h */

/* begin file include/ada/url_pattern_regex.h */
/**
 * @file url_search_params.h
 * @brief Declaration for the URL Search Params
 */
#ifndef ADA_URL_PATTERN_REGEX_H
#define ADA_URL_PATTERN_REGEX_H

#include <string>
#include <string_view>

#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER
#include <regex>
#endif  // ADA_USE_UNSAFE_STD_REGEX_PROVIDER

#if ADA_INCLUDE_URL_PATTERN
namespace ada::url_pattern_regex {

template <typename T>
concept regex_concept = requires(T t, std::string_view pattern,
                                 bool ignore_case, std::string_view input) {
  // Ensure the class has a type alias 'regex_type'
  typename T::regex_type;

  // Function to create a regex instance
  {
    T::create_instance(pattern, ignore_case)
  } -> std::same_as<std::optional<typename T::regex_type>>;

  // Function to perform regex search
  {
    T::regex_search(input, std::declval<typename T::regex_type&>())
  } -> std::same_as<std::optional<std::vector<std::optional<std::string>>>>;

  // Function to match regex pattern
  {
    T::regex_match(input, std::declval<typename T::regex_type&>())
  } -> std::same_as<bool>;

  // Copy constructor
  { T(std::declval<const T&>()) } -> std::same_as<T>;

  // Move constructor
  { T(std::declval<T&&>()) } -> std::same_as<T>;
};

#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER
class std_regex_provider final {
 public:
  std_regex_provider() = default;
  using regex_type = std::regex;
  static std::optional<regex_type> create_instance(std::string_view pattern,
                                                   bool ignore_case);
  static std::optional<std::vector<std::optional<std::string>>> regex_search(
      std::string_view input, const regex_type& pattern);
  static bool regex_match(std::string_view input, const regex_type& pattern);
};
#endif  // ADA_USE_UNSAFE_STD_REGEX_PROVIDER

}  // namespace ada::url_pattern_regex
#endif  // ADA_INCLUDE_URL_PATTERN
#endif  // ADA_URL_PATTERN_REGEX_H
/* end file include/ada/url_pattern_regex.h */
/* begin file include/ada/url_pattern_init.h */
/**
 * @file url_pattern_init.h
 * @brief Declaration for the url_pattern_init implementation.
 */
#ifndef ADA_URL_PATTERN_INIT_H
#define ADA_URL_PATTERN_INIT_H

/* begin file include/ada/errors.h */
/**
 * @file errors.h
 * @brief Error type definitions for URL parsing.
 *
 * Defines the error codes that can be returned when URL parsing fails.
 */
#ifndef ADA_ERRORS_H
#define ADA_ERRORS_H

#include <cstdint>
namespace ada {
/**
 * @brief Error codes for URL parsing operations.
 *
 * Used with `tl::expected` to indicate why a URL parsing operation failed.
 */
enum class errors : uint8_t {
  type_error /**< A type error occurred (e.g., invalid URL syntax). */
};
}  // namespace ada
#endif  // ADA_ERRORS_H
/* end file include/ada/errors.h */

#include <string_view>
#include <string>
#include <optional>
#include <iostream>

#if ADA_TESTING
#include <iostream>
#endif  // ADA_TESTING

#if ADA_INCLUDE_URL_PATTERN
namespace ada {

// Important: C++20 allows us to use concept rather than `using` or `typedef
// and allows functions with second argument, which is optional (using either
// std::nullopt or a parameter with default value)
template <typename F>
concept url_pattern_encoding_callback = requires(F f, std::string_view sv) {
  { f(sv) } -> std::same_as<tl::expected<std::string, errors>>;
};

// A structure providing matching patterns for individual components
// of a URL. When a URLPattern is created, or when a URLPattern is
// used to match or test against a URL, the input can be given as
// either a string or a URLPatternInit struct. If a string is given,
// it will be parsed to create a URLPatternInit. The URLPatternInit
// API is defined as part of the URLPattern specification.
// All provided strings must be valid UTF-8.
struct url_pattern_init {
  enum class process_type : uint8_t {
    url,
    pattern,
  };

  friend std::ostream& operator<<(std::ostream& os, process_type type) {
    switch (type) {
      case process_type::url:
        return os << "url";
      case process_type::pattern:
        return os << "pattern";
      default:
        return os << "unknown";
    }
  }

  // All strings must be valid UTF-8.
  // @see https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit
  static tl::expected<url_pattern_init, errors> process(
      const url_pattern_init& init, process_type type,
      std::optional<std::string_view> protocol = std::nullopt,
      std::optional<std::string_view> username = std::nullopt,
      std::optional<std::string_view> password = std::nullopt,
      std::optional<std::string_view> hostname = std::nullopt,
      std::optional<std::string_view> port = std::nullopt,
      std::optional<std::string_view> pathname = std::nullopt,
      std::optional<std::string_view> search = std::nullopt,
      std::optional<std::string_view> hash = std::nullopt);

  // @see https://urlpattern.spec.whatwg.org/#process-protocol-for-init
  static tl::expected<std::string, errors> process_protocol(
      std::string_view value, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-username-for-init
  static tl::expected<std::string, errors> process_username(
      std::string_view value, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-password-for-init
  static tl::expected<std::string, errors> process_password(
      std::string_view value, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-hostname-for-init
  static tl::expected<std::string, errors> process_hostname(
      std::string_view value, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-port-for-init
  static tl::expected<std::string, errors> process_port(
      std::string_view port, std::string_view protocol, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-pathname-for-init
  static tl::expected<std::string, errors> process_pathname(
      std::string_view value, std::string_view protocol, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-search-for-init
  static tl::expected<std::string, errors> process_search(
      std::string_view value, process_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-hash-for-init
  static tl::expected<std::string, errors> process_hash(std::string_view value,
                                                        process_type type);

#if ADA_TESTING
  friend void PrintTo(const url_pattern_init& init, std::ostream* os) {
    *os << "protocol: '" << init.protocol.value_or("undefined") << "', ";
    *os << "username: '" << init.username.value_or("undefined") << "', ";
    *os << "password: '" << init.password.value_or("undefined") << "', ";
    *os << "hostname: '" << init.hostname.value_or("undefined") << "', ";
    *os << "port: '" << init.port.value_or("undefined") << "', ";
    *os << "pathname: '" << init.pathname.value_or("undefined") << "', ";
    *os << "search: '" << init.search.value_or("undefined") << "', ";
    *os << "hash: '" << init.hash.value_or("undefined") << "', ";
    *os << "base_url: '" << init.base_url.value_or("undefined") << "', ";
  }
#endif  // ADA_TESTING

  bool operator==(const url_pattern_init&) const;
  // If present, must be valid UTF-8.
  std::optional<std::string> protocol{};
  // If present, must be valid UTF-8.
  std::optional<std::string> username{};
  // If present, must be valid UTF-8.
  std::optional<std::string> password{};
  // If present, must be valid UTF-8.
  std::optional<std::string> hostname{};
  // If present, must be valid UTF-8.
  std::optional<std::string> port{};
  // If present, must be valid UTF-8.
  std::optional<std::string> pathname{};
  // If present, must be valid UTF-8.
  std::optional<std::string> search{};
  // If present, must be valid UTF-8.
  std::optional<std::string> hash{};
  // If present, must be valid UTF-8.
  std::optional<std::string> base_url{};
};
}  // namespace ada
#endif  // ADA_INCLUDE_URL_PATTERN
#endif  // ADA_URL_PATTERN_INIT_H
/* end file include/ada/url_pattern_init.h */

/** @private Forward declarations */
namespace ada {
struct url_aggregator;
struct url;
#if ADA_INCLUDE_URL_PATTERN
template <url_pattern_regex::regex_concept regex_provider>
class url_pattern;
struct url_pattern_options;
#endif  // ADA_INCLUDE_URL_PATTERN
enum class errors : uint8_t;
}  // namespace ada

/**
 * @namespace ada::parser
 * @brief Internal URL parsing implementation.
 *
 * Contains the core URL parsing algorithm as specified by the WHATWG URL
 * Standard. These functions are used internally by `ada::parse()`.
 */
namespace ada::parser {
/**
 * Parses a URL string into a URL object.
 *
 * @tparam result_type The type of URL object to create (url or url_aggregator).
 *
 * @param user_input The URL string to parse (must be valid UTF-8).
 * @param base_url Optional base URL for resolving relative URLs.
 *
 * @return The parsed URL object. Check `is_valid` to determine if parsing
 *         succeeded.
 *
 * @see https://url.spec.whatwg.org/#concept-basic-url-parser
 */
template <typename result_type = url_aggregator>
result_type parse_url(std::string_view user_input,
                      const result_type* base_url = nullptr);

extern template url_aggregator parse_url<url_aggregator>(
    std::string_view user_input, const url_aggregator* base_url);
extern template url parse_url<url>(std::string_view user_input,
                                   const url* base_url);

template <typename result_type = url_aggregator, bool store_values = true>
result_type parse_url_impl(std::string_view user_input,
                           const result_type* base_url = nullptr);

extern template url_aggregator parse_url_impl<url_aggregator, true>(
    std::string_view user_input, const url_aggregator* base_url);
extern template url_aggregator parse_url_impl<url_aggregator, false>(
    std::string_view user_input, const url_aggregator* base_url);
extern template url parse_url_impl<url, true>(std::string_view user_input,
                                              const url* base_url);

/** @private */
template <class result_type>
bool try_parse_simple_absolute(std::string_view input, result_type& out);

#if ADA_INCLUDE_URL_PATTERN
template <url_pattern_regex::regex_concept regex_provider>
tl::expected<url_pattern<regex_provider>, errors> parse_url_pattern_impl(
    std::variant<std::string_view, url_pattern_init>&& input,
    const std::string_view* base_url, const url_pattern_options* options);
#endif  // ADA_INCLUDE_URL_PATTERN

}  // namespace ada::parser

#endif  // ADA_PARSER_H
/* end file include/ada/parser.h */
/* begin file include/ada/parser-inl.h */
/**
 * @file parser-inl.h
 */
#ifndef ADA_PARSER_INL_H
#define ADA_PARSER_INL_H

/* begin file include/ada/url_pattern.h */
/**
 * @file url_pattern.h
 * @brief URLPattern API implementation.
 *
 * This header provides the URLPattern API as specified by the WHATWG URL
 * Pattern Standard. URLPattern allows matching URLs against patterns with
 * wildcards and named groups, similar to how regular expressions match strings.
 *
 * @see https://urlpattern.spec.whatwg.org/
 * @see https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API
 */
#ifndef ADA_URL_PATTERN_H
#define ADA_URL_PATTERN_H

/* begin file include/ada/implementation.h */
/**
 * @file implementation.h
 * @brief User-facing functions for URL parsing and manipulation.
 *
 * This header provides the primary public API for parsing URLs in Ada.
 * It includes the main `ada::parse()` function which is the recommended
 * entry point for most users.
 *
 * @see https://url.spec.whatwg.org/#api
 */
#ifndef ADA_IMPLEMENTATION_H
#define ADA_IMPLEMENTATION_H

#include <string>
#include <string_view>
#include <optional>

/* begin file include/ada/url.h */
/**
 * @file url.h
 * @brief Declaration for the `ada::url` class.
 *
 * This file contains the `ada::url` struct which represents a parsed URL
 * using separate `std::string` instances for each component. This
 * representation is more flexible but uses more memory than `url_aggregator`.
 *
 * @see url_aggregator.h for a more memory-efficient alternative
 */
#ifndef ADA_URL_H
#define ADA_URL_H

#include <algorithm>
#include <optional>
#include <ostream>
#include <string>
#include <string_view>

/* begin file include/ada/url_components.h */
/**
 * @file url_components.h
 * @brief URL component offset representation for url_aggregator.
 *
 * This file defines the `url_components` struct which stores byte offsets
 * into a URL string buffer. It is used internally by `url_aggregator` to
 * efficiently locate URL components without storing separate strings.
 */
#ifndef ADA_URL_COMPONENTS_H
#define ADA_URL_COMPONENTS_H

namespace ada {

/**
 * @brief Stores byte offsets for URL components within a buffer.
 *
 * The `url_components` struct uses 32-bit offsets to track the boundaries
 * of each URL component within a single string buffer. This enables efficient
 * component extraction without additional memory allocations.
 *
 * Component layout in a URL:
 * ```
 * https://user:pass@example.com:1234/foo/bar?baz#quux
 *       |     |    |          | ^^^^|       |   |
 *       |     |    |          | |   |       |   `----- hash_start
 *       |     |    |          | |   |       `--------- search_start
 *       |     |    |          | |   `----------------- pathname_start
 *       |     |    |          | `--------------------- port
 *       |     |    |          `----------------------- host_end
 *       |     |    `---------------------------------- host_start
 *       |     `--------------------------------------- username_end
 *       `--------------------------------------------- protocol_end
 * ```
 *
 * @note The 32-bit offsets limit URLs to 4GB in length.
 * @note A value of `omitted` (UINT32_MAX) indicates the component is not
 * present.
 */
struct url_components {
  /** Sentinel value indicating a component is not present. */
  constexpr static uint32_t omitted = uint32_t(-1);

  url_components() = default;
  url_components(const url_components& u) = default;
  url_components(url_components&& u) noexcept = default;
  url_components& operator=(url_components&& u) noexcept = default;
  url_components& operator=(const url_components& u) = default;
  ~url_components() = default;

  /** Offset of the end of the protocol/scheme (position of ':'). */
  uint32_t protocol_end{0};

  /**
   * Offset of the end of the username.
   * Initialized to 0 (not `omitted`) to simplify username/password getters.
   */
  uint32_t username_end{0};

  /** Offset of the start of the host. */
  uint32_t host_start{0};

  /** Offset of the end of the host. */
  uint32_t host_end{0};

  /** Port number, or `omitted` if no port is specified. */
  uint32_t port{omitted};

  /** Offset of the start of the pathname. */
  uint32_t pathname_start{0};

  /** Offset of the '?' starting the query, or `omitted` if no query. */
  uint32_t search_start{omitted};

  /** Offset of the '#' starting the fragment, or `omitted` if no fragment. */
  uint32_t hash_start{omitted};

  /**
   * Validates that offsets are in ascending order and consistent.
   * Useful for debugging to detect internal corruption.
   * @return `true` if offsets are consistent, `false` otherwise.
   */
  [[nodiscard]] constexpr bool check_offset_consistency() const noexcept;

  /**
   * Returns a JSON string representation of the offsets for debugging.
   * @return A JSON-formatted string with all offset values.
   */
  [[nodiscard]] std::string to_string() const;

};  // struct url_components
}  // namespace ada
#endif
/* end file include/ada/url_components.h */

namespace ada {

struct url_aggregator;

// namespace parser {
// template <typename result_type>
// result_type parse_url(std::string_view user_input,
//                       const result_type* base_url = nullptr);
// template <typename result_type, bool store_values>
// result_type parse_url_impl(std::string_view user_input,
//                            const result_type* base_url = nullptr);
// }

/**
 * @brief Represents a parsed URL with individual string components.
 *
 * The `url` struct stores each URL component (scheme, username, password,
 * host, port, path, query, fragment) as a separate `std::string`. This
 * provides flexibility but incurs more memory allocations compared to
 * `url_aggregator`.
 *
 * **When to use `ada::url`:**
 * - When you need to frequently modify individual URL components
 * - When you want independent ownership of component strings
 *
 * **When to use `ada::url_aggregator` instead:**
 * - For read-mostly operations on parsed URLs
 * - When memory efficiency is important
 * - When you only need string_view access to components
 *
 * @note This type is returned when parsing with `ada::parse<ada::url>()`.
 *       By default, `ada::parse()` returns `ada::url_aggregator`.
 *
 * @see url_aggregator For a more memory-efficient URL representation
 * @see https://url.spec.whatwg.org/#url-representation
 */
struct url : url_base {
  url() = default;
  url(const url& u) = default;
  url(url&& u) noexcept = default;
  url& operator=(url&& u) noexcept = default;
  url& operator=(const url& u) = default;
  ~url() override = default;

  // Fields are ordered so that the most frequently accessed components
  // tend to occupy earlier cache lines and remain close together in memory.
  //
  // Note: The exact object layout (including cache-line boundaries, byte
  // offsets, and member sizes) is implementation- and platform-dependent.
  // This ordering expresses an intent for better cache locality but does not
  // guarantee any specific in-memory layout.

  /**
   * @private
   * A URL's host is null or a host. It is initially null.
   */
  std::optional<std::string> host{};

  /**
   * @private
   * A URL's path is either an ASCII string or a list of zero or more ASCII
   * strings, usually identifying a location.
   */
  std::string path{};

  /**
   * @private
   * A URL's query is either null or an ASCII string. It is initially null.
   */
  std::optional<std::string> query{};

  /**
   * @private
   * A URL's fragment is either null or an ASCII string that can be used for
   * further processing on the resource the URL's other components identify. It
   * is initially null.
   */
  std::optional<std::string> hash{};

  /**
   * @private
   * A URL's port is either null or a 16-bit unsigned integer that identifies a
   * networking port. It is initially null.
   */
  std::optional<uint16_t> port{};

  /**
   * @private
   * A URL's username is an ASCII string identifying a username. It is initially
   * the empty string.
   */
  std::string username{};

  /**
   * @private
   * A URL's password is an ASCII string identifying a password. It is initially
   * the empty string.
   */
  std::string password{};

  /**
   * Checks if the URL has an empty hostname (host is set but empty string).
   * @return `true` if host exists but is empty, `false` otherwise.
   */
  [[nodiscard]] inline bool has_empty_hostname() const noexcept;

  /**
   * Checks if the URL has a non-default port explicitly specified.
   * @return `true` if a port is present, `false` otherwise.
   */
  [[nodiscard]] inline bool has_port() const noexcept;

  /**
   * Checks if the URL has a hostname (including empty hostnames).
   * @return `true` if host is present, `false` otherwise.
   */
  [[nodiscard]] inline bool has_hostname() const noexcept;

  /**
   * Validates whether the hostname is a valid domain according to RFC 1034.
   * Checks that the domain and its labels have valid lengths (max 255 octets
   * total, max 63 octets per label).
   * @return `true` if the domain is valid, `false` otherwise.
   */
  [[nodiscard]] bool has_valid_domain() const noexcept override;

  /**
   * Returns a JSON string representation of this URL for debugging.
   * @return A JSON-formatted string with all URL components.
   */
  [[nodiscard]] std::string to_string() const override;

  /**
   * Returns the full serialized URL (the href).
   * @return The complete URL string (allocates a new string).
   * @see https://url.spec.whatwg.org/#dom-url-href
   */
  [[nodiscard]] ada_really_inline std::string get_href() const;

  /**
   * Returns the byte length of the serialized URL without allocating a string.
   * @return Size of the href in bytes.
   */
  [[nodiscard]] size_t get_href_size() const noexcept;

  /**
   * Returns the URL's origin as a string (scheme + host + port for special
   * URLs).
   * @return A newly allocated string containing the serialized origin.
   * @see https://url.spec.whatwg.org/#concept-url-origin
   */
  [[nodiscard]] std::string get_origin() const override;

  /**
   * Returns the URL's scheme followed by a colon (e.g., "https:").
   * @return A newly allocated string with the protocol.
   * @see https://url.spec.whatwg.org/#dom-url-protocol
   */
  [[nodiscard]] std::string get_protocol() const;

  /**
   * Returns the URL's host and port (e.g., "example.com:8080").
   * If no port is set, returns just the host. Returns empty string if no host.
   * @return A newly allocated string with host:port.
   * @see https://url.spec.whatwg.org/#dom-url-host
   */
  [[nodiscard]] std::string get_host() const;

  /**
   * Returns the URL's hostname (without port).
   * Returns empty string if no host is set.
   * @return A newly allocated string with the hostname.
   * @see https://url.spec.whatwg.org/#dom-url-hostname
   */
  [[nodiscard]] std::string get_hostname() const;

  /**
   * Returns the URL's path component.
   * @return A string_view pointing to the path.
   * @see https://url.spec.whatwg.org/#dom-url-pathname
   */
  [[nodiscard]] constexpr std::string_view get_pathname() const noexcept;

  /**
   * Returns the byte length of the pathname without creating a string.
   * @return Size of the pathname in bytes.
   * @see https://url.spec.whatwg.org/#dom-url-pathname
   */
  [[nodiscard]] ada_really_inline size_t get_pathname_length() const noexcept;

  /**
   * Returns the URL's query string prefixed with '?' (e.g., "?foo=bar").
   * Returns empty string if no query is set.
   * @return A newly allocated string with the search/query.
   * @see https://url.spec.whatwg.org/#dom-url-search
   */
  [[nodiscard]] std::string get_search() const;

  /**
   * Returns the URL's username component.
   * @return A constant reference to the username string.
   * @see https://url.spec.whatwg.org/#dom-url-username
   */
  [[nodiscard]] const std::string& get_username() const noexcept;

  /**
   * Sets the URL's username, percent-encoding special characters.
   * @param input The new username value.
   * @return `true` on success, `false` if the URL cannot have credentials.
   * @see https://url.spec.whatwg.org/#dom-url-username
   */
  bool set_username(std::string_view input);

  /**
   * Sets the URL's password, percent-encoding special characters.
   * @param input The new password value.
   * @return `true` on success, `false` if the URL cannot have credentials.
   * @see https://url.spec.whatwg.org/#dom-url-password
   */
  bool set_password(std::string_view input);

  /**
   * Sets the URL's port from a string (e.g., "8080").
   * @param input The port string. Empty string removes the port.
   * @return `true` on success, `false` if the URL cannot have a port.
   * @see https://url.spec.whatwg.org/#dom-url-port
   */
  bool set_port(std::string_view input);

  /**
   * Sets the URL's fragment/hash (the part after '#').
   * @param input The new hash value (with or without leading '#').
   * @see https://url.spec.whatwg.org/#dom-url-hash
   */
  void set_hash(std::string_view input);

  /**
   * Sets the URL's query string (the part after '?').
   * @param input The new query value (with or without leading '?').
   * @see https://url.spec.whatwg.org/#dom-url-search
   */
  void set_search(std::string_view input);

  /**
   * Sets the URL's pathname.
   * @param input The new path value.
   * @return `true` on success, `false` if the URL has an opaque path.
   * @see https://url.spec.whatwg.org/#dom-url-pathname
   */
  bool set_pathname(std::string_view input);

  /**
   * Sets the URL's host (hostname and optionally port).
   * @param input The new host value (e.g., "example.com:8080").
   * @return `true` on success, `false` if parsing fails.
   * @see https://url.spec.whatwg.org/#dom-url-host
   */
  bool set_host(std::string_view input);

  /**
   * Sets the URL's hostname (without port).
   * @param input The new hostname value.
   * @return `true` on success, `false` if parsing fails.
   * @see https://url.spec.whatwg.org/#dom-url-hostname
   */
  bool set_hostname(std::string_view input);

  /**
   * Sets the URL's protocol/scheme.
   * @param input The new protocol (with or without trailing ':').
   * @return `true` on success, `false` if the scheme is invalid.
   * @see https://url.spec.whatwg.org/#dom-url-protocol
   */
  bool set_protocol(std::string_view input);

  /**
   * Replaces the entire URL by parsing a new href string.
   * @param input The new URL string to parse.
   * @return `true` on success, `false` if parsing fails.
   * @see https://url.spec.whatwg.org/#dom-url-href
   */
  bool set_href(std::string_view input);

  /**
   * Returns the URL's password component.
   * @return A constant reference to the password string.
   * @see https://url.spec.whatwg.org/#dom-url-password
   */
  [[nodiscard]] const std::string& get_password() const noexcept;

  /**
   * Returns the URL's port as a string (e.g., "8080").
   * Returns empty string if no port is set.
   * @return A newly allocated string with the port.
   * @see https://url.spec.whatwg.org/#dom-url-port
   */
  [[nodiscard]] std::string get_port() const;

  /**
   * Returns the URL's fragment prefixed with '#' (e.g., "#section").
   * Returns empty string if no fragment is set.
   * @return A newly allocated string with the hash.
   * @see https://url.spec.whatwg.org/#dom-url-hash
   */
  [[nodiscard]] std::string get_hash() const;

  /**
   * Checks if the URL has credentials (non-empty username or password).
   * @return `true` if username or password is non-empty, `false` otherwise.
   */
  [[nodiscard]] ada_really_inline bool has_credentials() const noexcept;

  /**
   * Returns the URL component offsets for efficient serialization.
   *
   * The components represent byte offsets into the serialized URL:
   * ```
   * https://user:pass@example.com:1234/foo/bar?baz#quux
   *       |     |    |          | ^^^^|       |   |
   *       |     |    |          | |   |       |   `----- hash_start
   *       |     |    |          | |   |       `--------- search_start
   *       |     |    |          | |   `----------------- pathname_start
   *       |     |    |          | `--------------------- port
   *       |     |    |          `----------------------- host_end
   *       |     |    `---------------------------------- host_start
   *       |     `--------------------------------------- username_end
   *       `--------------------------------------------- protocol_end
   * ```
   * @return A newly constructed url_components struct.
   * @see https://github.com/servo/rust-url
   */
  [[nodiscard]] ada_really_inline ada::url_components get_components() const;

  /**
   * Checks if the URL has a fragment/hash component.
   * @return `true` if hash is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_hash() const noexcept override;

  /**
   * Checks if the URL has a query/search component.
   * @return `true` if query is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_search() const noexcept override;

 private:
  friend ada::url ada::parser::parse_url<ada::url>(std::string_view,
                                                   const ada::url*);
  friend ada::url_aggregator ada::parser::parse_url<ada::url_aggregator>(
      std::string_view, const ada::url_aggregator*);
  friend void ada::helpers::strip_trailing_spaces_from_opaque_path<ada::url>(
      ada::url& url);

  friend ada::url ada::parser::parse_url_impl<ada::url, true>(std::string_view,
                                                              const ada::url*);
  friend ada::url_aggregator ada::parser::parse_url_impl<
      ada::url_aggregator, true>(std::string_view, const ada::url_aggregator*);
  friend ada::url_aggregator ada::parser::parse_url_impl<
      ada::url_aggregator, false>(std::string_view, const ada::url_aggregator*);
  template <class result_type>
  friend bool ada::parser::try_parse_simple_absolute(std::string_view,
                                                     result_type&);

  inline void update_unencoded_base_hash(std::string_view input);
  inline void update_base_hostname(std::string_view input);
  inline void update_base_search(std::string_view input,
                                 const uint8_t query_percent_encode_set[]);
  inline void update_base_search(std::optional<std::string>&& input);
  inline void update_base_pathname(std::string_view input);
  inline void update_base_username(std::string_view input);
  inline void update_base_password(std::string_view input);
  inline void update_base_port(std::optional<uint16_t> input);

  /**
   * Sets the host or hostname according to override condition.
   * Return true on success.
   * @see https://url.spec.whatwg.org/#hostname-state
   */
  template <bool override_hostname = false>
  bool set_host_or_hostname(std::string_view input);

  /**
   * Return true on success.
   * @see https://url.spec.whatwg.org/#concept-ipv4-parser
   */
  [[nodiscard]] bool parse_ipv4(std::string_view input);

  /**
   * Return true on success.
   * @see https://url.spec.whatwg.org/#concept-ipv6-parser
   */
  [[nodiscard]] bool parse_ipv6(std::string_view input);

  /**
   * Return true on success.
   * @see https://url.spec.whatwg.org/#concept-opaque-host-parser
   */
  [[nodiscard]] bool parse_opaque_host(std::string_view input);

  /**
   * A URL's scheme is an ASCII string that identifies the type of URL and can
   * be used to dispatch a URL for further processing after parsing. It is
   * initially the empty string. We only set non_special_scheme when the scheme
   * is non-special, otherwise we avoid constructing string.
   *
   * Special schemes are stored in ada::scheme::details::is_special_list so we
   * typically do not need to store them in each url instance.
   */
  std::string non_special_scheme{};

  /**
   * A URL cannot have a username/password/port if its host is null or the empty
   * string, or its scheme is "file".
   */
  [[nodiscard]] inline bool cannot_have_credentials_or_port() const;

  ada_really_inline size_t parse_port(
      std::string_view view, bool check_trailing_content) noexcept override;

  ada_really_inline size_t parse_port(std::string_view view) noexcept override {
    return this->parse_port(view, false);
  }

  /**
   * Parse the host from the provided input. We assume that
   * the input does not contain spaces or tabs. Control
   * characters and spaces are not trimmed (they should have
   * been removed if needed).
   * Return true on success.
   * @see https://url.spec.whatwg.org/#host-parsing
   */
  [[nodiscard]] ada_really_inline bool parse_host(std::string_view input);

  template <bool has_state_override = false>
  [[nodiscard]] ada_really_inline bool parse_scheme(std::string_view input);

  constexpr void clear_pathname() override;
  constexpr void clear_search() override;
  constexpr void set_protocol_as_file();

  /**
   * Parse the path from the provided input.
   * Return true on success. Control characters not
   * trimmed from the ends (they should have
   * been removed if needed).
   *
   * The input is expected to be UTF-8.
   *
   * @see https://url.spec.whatwg.org/
   */
  ada_really_inline void parse_path(std::string_view input);

  /**
   * Set the scheme for this URL. The provided scheme should be a valid
   * scheme string, be lower-cased, not contain spaces or tabs. It should
   * have no spurious trailing or leading content.
   */
  inline void set_scheme(std::string&& new_scheme) noexcept;

  /**
   * Take the scheme from another URL. The scheme string is moved from the
   * provided url.
   */
  constexpr void copy_scheme(ada::url&& u);

  /**
   * Take the scheme from another URL. The scheme string is copied from the
   * provided url.
   */
  constexpr void copy_scheme(const ada::url& u);

};  // struct url

inline std::ostream& operator<<(std::ostream& out, const ada::url& u);
}  // namespace ada

#endif  // ADA_URL_H
/* end file include/ada/url.h */

namespace ada {

/**
 * Result type for URL parsing operations.
 *
 * Uses `tl::expected` to represent either a successfully parsed URL or an
 * error. This allows for exception-free error handling.
 *
 * @tparam result_type The URL type to return (default: `ada::url_aggregator`)
 *
 * @example
 * ```cpp
 * ada::result<ada::url_aggregator> result = ada::parse("https://example.com");
 * if (result) {
 *     // Success: use result.value() or *result
 * } else {
 *     // Error: handle result.error()
 * }
 * ```
 */
template <class result_type = ada::url_aggregator>
using result = tl::expected<result_type, ada::errors>;

/**
 * Parses a URL string according to the WHATWG URL Standard.
 *
 * This is the main entry point for URL parsing in Ada. The function takes
 * a string input and optionally a base URL for resolving relative URLs.
 *
 * @tparam result_type The URL type to return. Can be either `ada::url` or
 *         `ada::url_aggregator` (default). The `url_aggregator` type is more
 *         memory-efficient as it stores components as offsets into a single
 *         buffer.
 *
 * @param input The URL string to parse. Must be valid ASCII or UTF-8 encoded.
 *        Leading and trailing whitespace is automatically trimmed.
 * @param base_url Optional pointer to a base URL for resolving relative URLs.
 *        If nullptr (default), only absolute URLs can be parsed successfully.
 *
 * @return A `result<result_type>` containing either the parsed URL on success,
 *         or an error code on failure. Use the boolean conversion or
 *         `has_value()` to check for success.
 *
 * @note The parser is fully compliant with the WHATWG URL Standard.
 *
 * Parsing fails if the input or the resulting normalized URL exceeds
 * `get_max_input_length()` bytes (default ~4 GB, configurable via
 * `set_max_input_length()`). This accounts for percent-encoding expansion:
 * a short input that normalizes into a long URL is still rejected.
 *
 * @example
 * ```cpp
 * // Parse an absolute URL
 * auto url = ada::parse("https://user:pass@example.com:8080/path?query#hash");
 * if (url) {
 *     std::cout << url->get_hostname(); // "example.com"
 *     std::cout << url->get_pathname(); // "/path"
 * }
 *
 * // Parse a relative URL with a base
 * auto base = ada::parse("https://example.com/dir/");
 * if (base) {
 *     auto relative = ada::parse("../other/page", &*base);
 *     if (relative) {
 *         std::cout << relative->get_href(); //
 * "https://example.com/other/page"
 *     }
 * }
 * ```
 *
 * @see https://url.spec.whatwg.org/#url-parsing
 */
template <class result_type = ada::url_aggregator>
ada_warn_unused ada::result<result_type> parse(
    std::string_view input, const result_type* base_url = nullptr);

extern template ada::result<url> parse<url>(std::string_view input,
                                            const url* base_url);
extern template ada::result<url_aggregator> parse<url_aggregator>(
    std::string_view input, const url_aggregator* base_url);

/**
 * Checks whether a URL string can be successfully parsed.
 *
 * Equivalent to `parse(input, base).has_value()` for every input, including
 * when `set_max_input_length` rejects a normalized href that exceeds the limit.
 *
 * @param input The URL string to validate. Must be valid ASCII or UTF-8.
 * @param base_input Optional base URL string for relative inputs.
 * @return `true` if parsing would succeed, `false` otherwise.
 * @see https://url.spec.whatwg.org/#dom-url-canparse
 */
bool can_parse(std::string_view input,
               const std::string_view* base_input = nullptr);

#if ADA_INCLUDE_URL_PATTERN
/**
 * Parses a URL pattern according to the URLPattern specification.
 *
 * URL patterns provide a syntax for matching URLs against patterns, similar
 * to how regular expressions match strings. This is useful for routing and
 * URL-based dispatching.
 *
 * @tparam regex_provider The regex implementation to use for pattern matching.
 *
 * @param input Either a URL pattern string (valid UTF-8) or a URLPatternInit
 *        struct specifying individual component patterns.
 * @param base_url Optional pointer to a base URL string (valid UTF-8) for
 *        resolving relative patterns.
 * @param options Optional pointer to configuration options (e.g., ignore_case).
 *
 * @return A `tl::expected` containing either the parsed url_pattern on success,
 *         or an error code on failure.
 *
 * @see https://urlpattern.spec.whatwg.org
 */
template <url_pattern_regex::regex_concept regex_provider>
ada_warn_unused tl::expected<url_pattern<regex_provider>, errors>
parse_url_pattern(std::variant<std::string_view, url_pattern_init>&& input,
                  const std::string_view* base_url = nullptr,
                  const url_pattern_options* options = nullptr);
#endif  // ADA_INCLUDE_URL_PATTERN

/**
 * Converts a file system path to a file:// URL.
 *
 * Creates a properly formatted file URL from a local file system path.
 * Handles platform-specific path separators and percent-encoding.
 *
 * Respects `get_max_input_length()`: if the input path or the resulting
 * `file://` href would exceed the limit, returns an empty string.
 *
 * @param path The file system path to convert. Must be valid ASCII or UTF-8.
 *
 * @return A file:// URL string representing the given path, or empty on
 *         length-limit rejection.
 */
std::string href_from_file(std::string_view path);

/**
 * Sets the maximum allowed length for URLs.
 *
 * Both the raw input and the resulting normalized URL (the href) are checked
 * against this limit. Parsing or setter calls that would produce a URL
 * exceeding this length are rejected. The same limit also applies to
 * `href_from_file` and to query strings passed to `url_search_params`
 * construction / `reset`. The value must fit in a uint32_t.
 * The default is std::numeric_limits<uint32_t>::max() (approximately 4 GB).
 *
 * @param length The new maximum URL length in bytes.
 */
void set_max_input_length(uint32_t length);

/**
 * Returns the current maximum allowed length for URLs.
 *
 * @return The current maximum URL length in bytes.
 */
uint32_t get_max_input_length();

}  // namespace ada

#endif  // ADA_IMPLEMENTATION_H
/* end file include/ada/implementation.h */

#include <ostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <variant>
#include <vector>

#if ADA_TESTING
#include <iostream>
#endif  // ADA_TESTING

#if ADA_INCLUDE_URL_PATTERN
namespace ada {

enum class url_pattern_part_type : uint8_t {
  // The part represents a simple fixed text string.
  FIXED_TEXT,
  // The part represents a matching group with a custom regular expression.
  REGEXP,
  // The part represents a matching group that matches code points up to the
  // next separator code point. This is typically used for a named group like
  // ":foo" that does not have a custom regular expression.
  SEGMENT_WILDCARD,
  // The part represents a matching group that greedily matches all code points.
  // This is typically used for the "*" wildcard matching group.
  FULL_WILDCARD,
};

// Pattern type for fast-path matching optimization.
// This allows skipping expensive regex evaluation for common simple patterns.
enum class url_pattern_component_type : uint8_t {
  // Pattern is "^$" - only matches empty string
  EMPTY,
  // Pattern is "^<literal>$" - exact string match (no regex needed)
  EXACT_MATCH,
  // Pattern is "^(.*)$" - matches anything (full wildcard)
  FULL_WILDCARD,
  // Pattern requires actual regex evaluation
  REGEXP,
};

enum class url_pattern_part_modifier : uint8_t {
  // The part does not have a modifier.
  none,
  // The part has an optional modifier indicated by the U+003F (?) code point.
  optional,
  // The part has a "zero or more" modifier indicated by the U+002A (*) code
  // point.
  zero_or_more,
  // The part has a "one or more" modifier indicated by the U+002B (+) code
  // point.
  one_or_more,
};

// @see https://urlpattern.spec.whatwg.org/#part
class url_pattern_part {
 public:
  url_pattern_part(url_pattern_part_type _type, std::string&& _value,
                   url_pattern_part_modifier _modifier)
      : type(_type), value(std::move(_value)), modifier(_modifier) {}

  url_pattern_part(url_pattern_part_type _type, std::string&& _value,
                   url_pattern_part_modifier _modifier, std::string&& _name,
                   std::string&& _prefix, std::string&& _suffix)
      : type(_type),
        value(std::move(_value)),
        modifier(_modifier),
        name(std::move(_name)),
        prefix(std::move(_prefix)),
        suffix(std::move(_suffix)) {}
  // A part has an associated type, a string, which must be set upon creation.
  url_pattern_part_type type;
  // A part has an associated value, a string, which must be set upon creation.
  std::string value;
  // A part has an associated modifier a string, which must be set upon
  // creation.
  url_pattern_part_modifier modifier;
  // A part has an associated name, a string, initially the empty string.
  std::string name{};
  // A part has an associated prefix, a string, initially the empty string.
  std::string prefix{};
  // A part has an associated suffix, a string, initially the empty string.
  std::string suffix{};

  inline bool is_regexp() const noexcept;
};

// @see https://urlpattern.spec.whatwg.org/#options-header
struct url_pattern_compile_component_options {
  url_pattern_compile_component_options() = default;
  explicit url_pattern_compile_component_options(
      std::optional<char> new_delimiter = std::nullopt,
      std::optional<char> new_prefix = std::nullopt) noexcept
      : delimiter(new_delimiter), prefix(new_prefix) {}

  inline std::string_view get_delimiter() const ada_warn_unused;
  inline std::string_view get_prefix() const ada_warn_unused;

  // @see https://urlpattern.spec.whatwg.org/#options-ignore-case
  bool ignore_case = false;

  static url_pattern_compile_component_options DEFAULT;
  static url_pattern_compile_component_options HOSTNAME;
  static url_pattern_compile_component_options PATHNAME;

 private:
  // @see https://urlpattern.spec.whatwg.org/#options-delimiter-code-point
  std::optional<char> delimiter{};
  // @see https://urlpattern.spec.whatwg.org/#options-prefix-code-point
  std::optional<char> prefix{};
};

// The default options is an options struct with delimiter code point set to
// the empty string and prefix code point set to the empty string.
inline url_pattern_compile_component_options
    url_pattern_compile_component_options::DEFAULT(std::nullopt, std::nullopt);

// The hostname options is an options struct with delimiter code point set
// "." and prefix code point set to the empty string.
inline url_pattern_compile_component_options
    url_pattern_compile_component_options::HOSTNAME('.', std::nullopt);

// The pathname options is an options struct with delimiter code point set
// "/" and prefix code point set to "/".
inline url_pattern_compile_component_options
    url_pattern_compile_component_options::PATHNAME('/', '/');

// A struct providing the URLPattern matching results for a single
// URL component. The URLPatternComponentResult is only ever used
// as a member attribute of a URLPatternResult struct. The
// URLPatternComponentResult API is defined as part of the URLPattern
// specification.
struct url_pattern_component_result {
  std::string input;
  std::unordered_map<std::string, std::optional<std::string>> groups;

  bool operator==(const url_pattern_component_result&) const;

#if ADA_TESTING
  friend void PrintTo(const url_pattern_component_result& result,
                      std::ostream* os) {
    *os << "input: '" << result.input << "', group: ";
    for (const auto& group : result.groups) {
      *os << "(" << group.first << ", " << group.second.value_or("undefined")
          << ") ";
    }
  }
#endif  // ADA_TESTING
};

template <url_pattern_regex::regex_concept regex_provider>
class url_pattern_component {
 public:
  url_pattern_component() = default;

  // This function explicitly takes a std::string because it is moved.
  // To avoid unnecessary copy, move each value while calling the constructor.
  url_pattern_component(std::string&& new_pattern,
                        typename regex_provider::regex_type&& new_regexp,
                        std::vector<std::string>&& new_group_name_list,
                        bool new_has_regexp_groups,
                        url_pattern_component_type new_type,
                        std::string&& new_exact_match_value = {})
      : regexp(std::move(new_regexp)),
        pattern(std::move(new_pattern)),
        group_name_list(std::move(new_group_name_list)),
        exact_match_value(std::move(new_exact_match_value)),
        has_regexp_groups(new_has_regexp_groups),
        type(new_type) {}

  // @see https://urlpattern.spec.whatwg.org/#compile-a-component
  template <url_pattern_encoding_callback F>
  static tl::expected<url_pattern_component, errors> compile(
      std::string_view input, F& encoding_callback,
      url_pattern_compile_component_options& options);

  // @see https://urlpattern.spec.whatwg.org/#create-a-component-match-result
  url_pattern_component_result create_component_match_result(
      std::string&& input,
      std::vector<std::optional<std::string>>&& exec_result);

  // Fast path test that returns true/false without constructing result groups.
  // Uses cached pattern type to skip regex evaluation for simple patterns.
  bool fast_test(std::string_view input) const noexcept;

  // Fast path match that returns capture groups without regex for simple
  // patterns. Returns nullopt if pattern doesn't match, otherwise returns
  // capture groups.
  std::optional<std::vector<std::optional<std::string>>> fast_match(
      std::string_view input) const;

#if ADA_TESTING
  friend void PrintTo(const url_pattern_component& component,
                      std::ostream* os) {
    *os << "pattern: '" << component.pattern
        << "', has_regexp_groups: " << component.has_regexp_groups
        << "group_name_list: ";
    for (const auto& name : component.group_name_list) {
      *os << name << ", ";
    }
  }
#endif  // ADA_TESTING

  typename regex_provider::regex_type regexp{};
  std::string pattern{};
  std::vector<std::string> group_name_list{};
  // For EXACT_MATCH type: the literal string to compare against
  std::string exact_match_value{};
  bool has_regexp_groups = false;
  // Cached pattern type for fast-path optimization
  url_pattern_component_type type = url_pattern_component_type::REGEXP;
};

// A URLPattern input can be either a string or a URLPatternInit object.
// If it is a string, it must be a valid UTF-8 string.
using url_pattern_input = std::variant<std::string_view, url_pattern_init>;

// A struct providing the URLPattern matching results for all
// components of a URL. The URLPatternResult API is defined as
// part of the URLPattern specification.
struct url_pattern_result {
  std::vector<url_pattern_input> inputs;
  url_pattern_component_result protocol;
  url_pattern_component_result username;
  url_pattern_component_result password;
  url_pattern_component_result hostname;
  url_pattern_component_result port;
  url_pattern_component_result pathname;
  url_pattern_component_result search;
  url_pattern_component_result hash;
};

struct url_pattern_options {
  bool ignore_case = false;

#if ADA_TESTING
  friend void PrintTo(const url_pattern_options& options, std::ostream* os) {
    *os << "ignore_case: '" << options.ignore_case;
  }
#endif  // ADA_TESTING
};

/**
 * @brief URL pattern matching class implementing the URLPattern API.
 *
 * URLPattern provides a way to match URLs against patterns with wildcards
 * and named capture groups. It's useful for routing, URL-based dispatching,
 * and URL validation.
 *
 * Pattern syntax supports:
 * - Literal text matching
 * - Named groups: `:name` (matches up to the next separator)
 * - Wildcards: `*` (matches everything)
 * - Custom regex: `(pattern)`
 * - Optional segments: `:name?`
 * - Repeated segments: `:name+`, `:name*`
 *
 * @tparam regex_provider The regex implementation to use for pattern matching.
 *         Must satisfy the url_pattern_regex::regex_concept.
 *
 * @note All string inputs must be valid UTF-8.
 *
 * @see https://urlpattern.spec.whatwg.org/
 */
template <url_pattern_regex::regex_concept regex_provider>
class url_pattern {
 public:
  url_pattern() = default;

  /**
   * If non-null, base_url must pointer at a valid UTF-8 string.
   * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec
   */
  result<std::optional<url_pattern_result>> exec(
      const url_pattern_input& input,
      const std::string_view* base_url = nullptr);

  /**
   * If non-null, base_url must pointer at a valid UTF-8 string.
   * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-test
   */
  result<bool> test(const url_pattern_input& input,
                    const std::string_view* base_url = nullptr);

  /**
   * @see https://urlpattern.spec.whatwg.org/#url-pattern-match
   * This function expects a valid UTF-8 string if input is a string.
   */
  result<std::optional<url_pattern_result>> match(
      const url_pattern_input& input,
      const std::string_view* base_url_string = nullptr);

  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol
  [[nodiscard]] std::string_view get_protocol() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-username
  [[nodiscard]] std::string_view get_username() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-password
  [[nodiscard]] std::string_view get_password() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname
  [[nodiscard]] std::string_view get_hostname() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-port
  [[nodiscard]] std::string_view get_port() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname
  [[nodiscard]] std::string_view get_pathname() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-search
  [[nodiscard]] std::string_view get_search() const ada_lifetime_bound;
  // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash
  [[nodiscard]] std::string_view get_hash() const ada_lifetime_bound;

  // If ignoreCase is true, the JavaScript regular expression created for each
  // pattern must use the `vi` flag. Otherwise, they must use the `v` flag.
  [[nodiscard]] bool ignore_case() const;

  // @see https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups
  [[nodiscard]] bool has_regexp_groups() const;

  // Helper to test all components at once. Returns true if all match.
  [[nodiscard]] bool test_components(
      std::string_view protocol, std::string_view username,
      std::string_view password, std::string_view hostname,
      std::string_view port, std::string_view pathname, std::string_view search,
      std::string_view hash) const;

#if ADA_TESTING
  friend void PrintTo(const url_pattern& c, std::ostream* os) {
    *os << "protocol_component: '" << c.get_protocol() << ", ";
    *os << "username_component: '" << c.get_username() << ", ";
    *os << "password_component: '" << c.get_password() << ", ";
    *os << "hostname_component: '" << c.get_hostname() << ", ";
    *os << "port_component: '" << c.get_port() << ", ";
    *os << "pathname_component: '" << c.get_pathname() << ", ";
    *os << "search_component: '" << c.get_search() << ", ";
    *os << "hash_component: '" << c.get_hash();
  }
#endif  // ADA_TESTING

  template <url_pattern_regex::regex_concept P>
  friend tl::expected<url_pattern<P>, errors> parser::parse_url_pattern_impl(
      std::variant<std::string_view, url_pattern_init>&& input,
      const std::string_view* base_url, const url_pattern_options* options);

  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> protocol_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> username_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> password_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> hostname_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> port_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> pathname_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> search_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  url_pattern_component<regex_provider> hash_component{};
  /**
   * @private
   * We can not make this private due to a LLVM bug.
   * Ref: https://github.com/ada-url/ada/pull/859
   */
  bool ignore_case_ = false;
};
}  // namespace ada
#endif  // ADA_INCLUDE_URL_PATTERN
#endif
/* end file include/ada/url_pattern.h */
/* begin file include/ada/url_pattern_helpers.h */
/**
 * @file url_pattern_helpers.h
 * @brief Declaration for the URLPattern helpers.
 */
#ifndef ADA_URL_PATTERN_HELPERS_H
#define ADA_URL_PATTERN_HELPERS_H


#include <string>
#include <tuple>
#include <vector>

#if ADA_INCLUDE_URL_PATTERN
namespace ada {
enum class errors : uint8_t;
}

namespace ada::url_pattern_helpers {

// @see https://urlpattern.spec.whatwg.org/#token
enum class token_type : uint8_t {
  INVALID_CHAR,    // 0
  OPEN,            // 1
  CLOSE,           // 2
  REGEXP,          // 3
  NAME,            // 4
  CHAR,            // 5
  ESCAPED_CHAR,    // 6
  OTHER_MODIFIER,  // 7
  ASTERISK,        // 8
  END,             // 9
};

#ifdef ADA_TESTING
std::string to_string(token_type type);
#endif  // ADA_TESTING

// @see https://urlpattern.spec.whatwg.org/#tokenize-policy
enum class token_policy {
  strict,
  lenient,
};

// @see https://urlpattern.spec.whatwg.org/#tokens
class token {
 public:
  token(token_type _type, size_t _index, std::string_view _value)
      : type(_type), index(_index), value(_value) {}

  // A token has an associated type, a string, initially "invalid-char".
  token_type type = token_type::INVALID_CHAR;

  // A token has an associated index, a number, initially 0. It is the position
  // of the first code point in the pattern string represented by the token.
  size_t index = 0;

  // A token has an associated value, a string, initially the empty string. It
  // contains the code points from the pattern string represented by the token.
  std::string_view value{};
};

// @see https://urlpattern.spec.whatwg.org/#pattern-parser
template <url_pattern_encoding_callback F>
class url_pattern_parser {
 public:
  url_pattern_parser(F& encoding_callback_,
                     std::string_view segment_wildcard_regexp_)
      : encoding_callback(encoding_callback_),
        segment_wildcard_regexp(segment_wildcard_regexp_) {}

  bool can_continue() const { return index < tokens.size(); }

  // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-token
  token* try_consume_token(token_type type);
  // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token
  token* try_consume_modifier_token();
  // @see
  // https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token
  token* try_consume_regexp_or_wildcard_token(const token* name_token);
  // @see https://urlpattern.spec.whatwg.org/#consume-text
  std::string consume_text();
  // @see https://urlpattern.spec.whatwg.org/#consume-a-required-token
  bool consume_required_token(token_type type);
  // @see
  // https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value
  std::optional<errors> maybe_add_part_from_the_pending_fixed_value()
      ada_warn_unused;
  // @see https://urlpattern.spec.whatwg.org/#add-a-part
  std::optional<errors> add_part(std::string_view prefix, token* name_token,
                                 token* regexp_or_wildcard_token,
                                 std::string_view suyffix,
                                 token* modifier_token) ada_warn_unused;

  std::vector<token> tokens{};
  F& encoding_callback;
  std::string segment_wildcard_regexp;
  std::vector<url_pattern_part> parts{};
  std::string pending_fixed_value{};
  size_t index = 0;
  size_t next_numeric_name = 0;
};

// @see https://urlpattern.spec.whatwg.org/#tokenizer
class Tokenizer {
 public:
  explicit Tokenizer(std::string_view new_input, token_policy new_policy)
      : input(new_input), policy(new_policy) {}

  // @see https://urlpattern.spec.whatwg.org/#get-the-next-code-point
  constexpr void get_next_code_point();

  // True when the most recent decoded unit was malformed UTF-8.
  bool had_invalid_code_point() const { return invalid_code_point; }

  // @see https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point
  constexpr void seek_and_get_next_code_point(size_t index);

  // @see https://urlpattern.spec.whatwg.org/#add-a-token

  void add_token(token_type type, size_t next_position, size_t value_position,
                 size_t value_length);

  // @see https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length
  void add_token_with_default_length(token_type type, size_t next_position,
                                     size_t value_position);

  // @see
  // https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length
  void add_token_with_defaults(token_type type);

  // @see https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error
  std::optional<errors> process_tokenizing_error(
      size_t next_position, size_t value_position) ada_warn_unused;

  friend tl::expected<std::vector<token>, errors> tokenize(
      std::string_view input, token_policy policy);

 private:
  // has an associated input, a pattern string, initially the empty string.
  std::string_view input;
  // has an associated policy, a tokenize policy, initially "strict".
  token_policy policy;
  // has an associated token list, a token list, initially an empty list.
  std::vector<token> token_list{};
  // has an associated index, a number, initially 0.
  size_t index = 0;
  // has an associated next index, a number, initially 0.
  size_t next_index = 0;
  // has an associated code point, a Unicode code point, initially null.
  char32_t code_point{};
  // Tracks whether the last decoded code point was malformed UTF-8.
  bool invalid_code_point = false;
};

// @see https://urlpattern.spec.whatwg.org/#constructor-string-parser
template <url_pattern_regex::regex_concept regex_provider>
struct constructor_string_parser {
  explicit constructor_string_parser(std::string_view new_input,
                                     std::vector<token>&& new_token_list)
      : input(new_input), token_list(std::move(new_token_list)) {}
  // @see https://urlpattern.spec.whatwg.org/#parse-a-constructor-string
  static tl::expected<url_pattern_init, errors> parse(std::string_view input);

  // @see https://urlpattern.spec.whatwg.org/#constructor-string-parser-state
  enum class State {
    INIT,
    PROTOCOL,
    AUTHORITY,
    USERNAME,
    PASSWORD,
    HOSTNAME,
    PORT,
    PATHNAME,
    SEARCH,
    HASH,
    DONE,
  };

  // @see
  // https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag
  std::optional<errors> compute_protocol_matches_special_scheme_flag();

 private:
  // @see https://urlpattern.spec.whatwg.org/#rewind
  constexpr void rewind();

  // @see https://urlpattern.spec.whatwg.org/#is-a-hash-prefix
  constexpr bool is_hash_prefix();

  // @see https://urlpattern.spec.whatwg.org/#is-a-search-prefix
  constexpr bool is_search_prefix();

  // @see https://urlpattern.spec.whatwg.org/#change-state
  void change_state(State state, size_t skip);

  // @see https://urlpattern.spec.whatwg.org/#is-a-group-open
  constexpr bool is_group_open() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-group-close
  constexpr bool is_group_close() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-protocol-suffix
  constexpr bool is_protocol_suffix() const;

  // @see https://urlpattern.spec.whatwg.org/#next-is-authority-slashes
  constexpr bool next_is_authority_slashes() const;

  // @see https://urlpattern.spec.whatwg.org/#is-an-identity-terminator
  constexpr bool is_an_identity_terminator() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-pathname-start
  constexpr bool is_pathname_start() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-password-prefix
  constexpr bool is_password_prefix() const;

  // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-open
  constexpr bool is_an_ipv6_open() const;

  // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-close
  constexpr bool is_an_ipv6_close() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-port-prefix
  constexpr bool is_port_prefix() const;

  // @see https://urlpattern.spec.whatwg.org/#is-a-non-special-pattern-char
  constexpr bool is_non_special_pattern_char(size_t index,
                                             uint32_t value) const;

  // @see https://urlpattern.spec.whatwg.org/#get-a-safe-token
  constexpr const token* get_safe_token(size_t index) const;

  // @see https://urlpattern.spec.whatwg.org/#make-a-component-string
  std::string make_component_string();
  // has an associated input, a string, which must be set upon creation.
  std::string_view input;
  // has an associated token list, a token list, which must be set upon
  // creation.
  std::vector<token> token_list;
  // has an associated result, a URLPatternInit, initially set to a new
  // URLPatternInit.
  url_pattern_init result{};
  // has an associated component start, a number, initially set to 0.
  size_t component_start = 0;
  // has an associated token index, a number, initially set to 0.
  size_t token_index = 0;
  // has an associated token increment, a number, initially set to 1.
  size_t token_increment = 1;
  // has an associated group depth, a number, initially set to 0.
  size_t group_depth = 0;
  // has an associated hostname IPv6 bracket depth, a number, initially set to
  // 0.
  size_t hostname_ipv6_bracket_depth = 0;
  // has an associated protocol matches a special scheme flag, a boolean,
  // initially set to false.
  bool protocol_matches_a_special_scheme_flag = false;
  // has an associated state, a string, initially set to "init".
  State state = State::INIT;
};

// @see https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol
tl::expected<std::string, errors> canonicalize_protocol(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-username
tl::expected<std::string, errors> canonicalize_username(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-password
tl::expected<std::string, errors> canonicalize_password(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-password
tl::expected<std::string, errors> canonicalize_hostname(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-an-ipv6-hostname
tl::expected<std::string, errors> canonicalize_ipv6_hostname(
    std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-port
tl::expected<std::string, errors> canonicalize_port(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-port
tl::expected<std::string, errors> canonicalize_port_with_protocol(
    std::string_view input, std::string_view protocol);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-pathname
tl::expected<std::string, errors> canonicalize_pathname(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-an-opaque-pathname
tl::expected<std::string, errors> canonicalize_opaque_pathname(
    std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-search
tl::expected<std::string, errors> canonicalize_search(std::string_view input);

// @see https://wicg.github.io/urlpattern/#canonicalize-a-hash
tl::expected<std::string, errors> canonicalize_hash(std::string_view input);

// @see https://urlpattern.spec.whatwg.org/#tokenize
tl::expected<std::vector<token>, errors> tokenize(std::string_view input,
                                                  token_policy policy);

// @see https://urlpattern.spec.whatwg.org/#process-a-base-url-string
std::string process_base_url_string(std::string_view input,
                                    url_pattern_init::process_type type);

// @see https://urlpattern.spec.whatwg.org/#escape-a-pattern-string
std::string escape_pattern_string(std::string_view input);

// @see https://urlpattern.spec.whatwg.org/#escape-a-regexp-string
std::string escape_regexp_string(std::string_view input);

// @see https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname
constexpr bool is_absolute_pathname(
    std::string_view input, url_pattern_init::process_type type) noexcept;

// @see https://urlpattern.spec.whatwg.org/#parse-a-pattern-string
template <url_pattern_encoding_callback F>
tl::expected<std::vector<url_pattern_part>, errors> parse_pattern_string(
    std::string_view input, url_pattern_compile_component_options& options,
    F& encoding_callback);

// @see https://urlpattern.spec.whatwg.org/#generate-a-pattern-string
std::string generate_pattern_string(
    std::vector<url_pattern_part>& part_list,
    url_pattern_compile_component_options& options);

// @see
// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list
std::tuple<std::string, std::vector<std::string>>
generate_regular_expression_and_name_list(
    const std::vector<url_pattern_part>& part_list,
    url_pattern_compile_component_options options);

// @see https://urlpattern.spec.whatwg.org/#hostname-pattern-is-an-ipv6-address
bool is_ipv6_address(std::string_view input) noexcept;

// @see
// https://urlpattern.spec.whatwg.org/#protocol-component-matches-a-special-scheme
template <url_pattern_regex::regex_concept regex_provider>
bool protocol_component_matches_special_scheme(
    ada::url_pattern_component<regex_provider>& input);

// @see https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string
std::string_view convert_modifier_to_string(url_pattern_part_modifier modifier);

// @see https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp
std::string generate_segment_wildcard_regexp(
    url_pattern_compile_component_options options);

}  // namespace ada::url_pattern_helpers
#endif  // ADA_INCLUDE_URL_PATTERN
#endif
/* end file include/ada/url_pattern_helpers.h */

#include <string>
#include <string_view>
#include <variant>

namespace ada::parser {
#if ADA_INCLUDE_URL_PATTERN
template <url_pattern_regex::regex_concept regex_provider>
tl::expected<url_pattern<regex_provider>, errors> parse_url_pattern_impl(
    std::variant<std::string_view, url_pattern_init>&& input,
    const std::string_view* base_url, const url_pattern_options* options) {
  // Let init be null.
  url_pattern_init init;

  // If input is a scalar value string then:
  if (std::holds_alternative<std::string_view>(input)) {
    // Set init to the result of running parse a constructor string given input.
    auto parse_result =
        url_pattern_helpers::constructor_string_parser<regex_provider>::parse(
            std::get<std::string_view>(input));
    if (!parse_result) {
      ada_log("constructor_string_parser::parse failed");
      return tl::unexpected(parse_result.error());
    }
    init = std::move(*parse_result);
    // If baseURL is null and init["protocol"] does not exist, then throw a
    // TypeError.
    if (!base_url && !init.protocol) {
      ada_log("base url is null and protocol is not set");
      return tl::unexpected(errors::type_error);
    }

    // If baseURL is not null, set init["baseURL"] to baseURL.
    if (base_url) {
      init.base_url = std::string(*base_url);
    }
  } else {
    // Assert: input is a URLPatternInit.
    ADA_ASSERT_TRUE(std::holds_alternative<url_pattern_init>(input));
    // If baseURL is not null, then throw a TypeError.
    if (base_url) {
      ada_log("base url is not null");
      return tl::unexpected(errors::type_error);
    }
    // Optimization: Avoid copy by moving the input value.
    // Set init to input.
    init = std::move(std::get<url_pattern_init>(input));
  }

  // Let processedInit be the result of process a URLPatternInit given init,
  // "pattern", null, null, null, null, null, null, null, and null.
  auto processed_init =
      url_pattern_init::process(init, url_pattern_init::process_type::pattern);
  if (!processed_init) {
    ada_log("url_pattern_init::process failed for init and 'pattern'");
    return tl::unexpected(processed_init.error());
  }

  // For each componentName of  "protocol", "username", "password", "hostname",
  // "port", "pathname", "search", "hash" If processedInit[componentName] does
  // not exist, then set processedInit[componentName] to "*".
  ADA_ASSERT_TRUE(processed_init.has_value());
  if (!processed_init->protocol) processed_init->protocol = "*";
  if (!processed_init->username) processed_init->username = "*";
  if (!processed_init->password) processed_init->password = "*";
  if (!processed_init->hostname) processed_init->hostname = "*";
  if (!processed_init->port) processed_init->port = "*";
  if (!processed_init->pathname) processed_init->pathname = "*";
  if (!processed_init->search) processed_init->search = "*";
  if (!processed_init->hash) processed_init->hash = "*";

  ada_log("-- processed_init->protocol: ", processed_init->protocol.value());
  ada_log("-- processed_init->username: ", processed_init->username.value());
  ada_log("-- processed_init->password: ", processed_init->password.value());
  ada_log("-- processed_init->hostname: ", processed_init->hostname.value());
  ada_log("-- processed_init->port: ", processed_init->port.value());
  ada_log("-- processed_init->pathname: ", processed_init->pathname.value());
  ada_log("-- processed_init->search: ", processed_init->search.value());
  ada_log("-- processed_init->hash: ", processed_init->hash.value());

  // If processedInit["protocol"] is a special scheme and processedInit["port"]
  // is a string which represents its corresponding default port in radix-10
  // using ASCII digits then set processedInit["port"] to the empty string.
  // TODO: Optimization opportunity.
  if (scheme::is_special(*processed_init->protocol)) {
    std::string_view port = processed_init->port.value();
    if (std::to_string(scheme::get_special_port(*processed_init->protocol)) ==
        port) {
      processed_init->port->clear();
    }
  }

  // Let urlPattern be a new URL pattern.
  url_pattern<regex_provider> url_pattern_{};

  // Set urlPattern's protocol component to the result of compiling a component
  // given processedInit["protocol"], canonicalize a protocol, and default
  // options.
  auto protocol_component = url_pattern_component<regex_provider>::compile(
      processed_init->protocol.value(),
      url_pattern_helpers::canonicalize_protocol,
      url_pattern_compile_component_options::DEFAULT);
  if (!protocol_component) {
    ada_log("url_pattern_component::compile failed for protocol ",
            processed_init->protocol.value());
    return tl::unexpected(protocol_component.error());
  }
  url_pattern_.protocol_component = std::move(*protocol_component);

  // Set urlPattern's username component to the result of compiling a component
  // given processedInit["username"], canonicalize a username, and default
  // options.
  auto username_component = url_pattern_component<regex_provider>::compile(
      processed_init->username.value(),
      url_pattern_helpers::canonicalize_username,
      url_pattern_compile_component_options::DEFAULT);
  if (!username_component) {
    ada_log("url_pattern_component::compile failed for username ",
            processed_init->username.value());
    return tl::unexpected(username_component.error());
  }
  url_pattern_.username_component = std::move(*username_component);

  // Set urlPattern's password component to the result of compiling a component
  // given processedInit["password"], canonicalize a password, and default
  // options.
  auto password_component = url_pattern_component<regex_provider>::compile(
      processed_init->password.value(),
      url_pattern_helpers::canonicalize_password,
      url_pattern_compile_component_options::DEFAULT);
  if (!password_component) {
    ada_log("url_pattern_component::compile failed for password ",
            processed_init->password.value());
    return tl::unexpected(password_component.error());
  }
  url_pattern_.password_component = std::move(*password_component);

  // TODO: Optimization opportunity. The following if statement can be
  // simplified.
  // If the result running hostname pattern is an IPv6 address given
  // processedInit["hostname"] is true, then set urlPattern's hostname component
  // to the result of compiling a component given processedInit["hostname"],
  // canonicalize an IPv6 hostname, and hostname options.
  if (url_pattern_helpers::is_ipv6_address(processed_init->hostname.value())) {
    ada_log("processed_init->hostname is ipv6 address");
    // then set urlPattern's hostname component to the result of compiling a
    // component given processedInit["hostname"], canonicalize an IPv6 hostname,
    // and hostname options.
    auto hostname_component = url_pattern_component<regex_provider>::compile(
        processed_init->hostname.value(),
        url_pattern_helpers::canonicalize_ipv6_hostname,
        url_pattern_compile_component_options::DEFAULT);
    if (!hostname_component) {
      ada_log("url_pattern_component::compile failed for ipv6 hostname ",
              processed_init->hostname.value());
      return tl::unexpected(hostname_component.error());
    }
    url_pattern_.hostname_component = std::move(*hostname_component);
  } else {
    // Otherwise, set urlPattern's hostname component to the result of compiling
    // a component given processedInit["hostname"], canonicalize a hostname, and
    // hostname options.
    auto hostname_component = url_pattern_component<regex_provider>::compile(
        processed_init->hostname.value(),
        url_pattern_helpers::canonicalize_hostname,
        url_pattern_compile_component_options::HOSTNAME);
    if (!hostname_component) {
      ada_log("url_pattern_component::compile failed for hostname ",
              processed_init->hostname.value());
      return tl::unexpected(hostname_component.error());
    }
    url_pattern_.hostname_component = std::move(*hostname_component);
  }

  // Set urlPattern's port component to the result of compiling a component
  // given processedInit["port"], canonicalize a port, and default options.
  auto port_component = url_pattern_component<regex_provider>::compile(
      processed_init->port.value(), url_pattern_helpers::canonicalize_port,
      url_pattern_compile_component_options::DEFAULT);
  if (!port_component) {
    ada_log("url_pattern_component::compile failed for port ",
            processed_init->port.value());
    return tl::unexpected(port_component.error());
  }
  url_pattern_.port_component = std::move(*port_component);

  // Let compileOptions be a copy of the default options with the ignore case
  // property set to options["ignoreCase"].
  auto compile_options = url_pattern_compile_component_options::DEFAULT;
  if (options) {
    compile_options.ignore_case = options->ignore_case;
  }

  // TODO: Optimization opportunity: Simplify this if statement.
  // If the result of running protocol component matches a special scheme given
  // urlPattern's protocol component is true, then:
  if (url_pattern_helpers::protocol_component_matches_special_scheme<
          regex_provider>(url_pattern_.protocol_component)) {
    // Let pathCompileOptions be copy of the pathname options with the ignore
    // case property set to options["ignoreCase"].
    auto path_compile_options = url_pattern_compile_component_options::PATHNAME;
    if (options) {
      path_compile_options.ignore_case = options->ignore_case;
    }

    // Set urlPattern's pathname component to the result of compiling a
    // component given processedInit["pathname"], canonicalize a pathname, and
    // pathCompileOptions.
    auto pathname_component = url_pattern_component<regex_provider>::compile(
        processed_init->pathname.value(),
        url_pattern_helpers::canonicalize_pathname, path_compile_options);
    if (!pathname_component) {
      ada_log("url_pattern_component::compile failed for pathname ",
              processed_init->pathname.value());
      return tl::unexpected(pathname_component.error());
    }
    url_pattern_.pathname_component = std::move(*pathname_component);
  } else {
    // Otherwise set urlPattern's pathname component to the result of compiling
    // a component given processedInit["pathname"], canonicalize an opaque
    // pathname, and compileOptions.
    auto pathname_component = url_pattern_component<regex_provider>::compile(
        processed_init->pathname.value(),
        url_pattern_helpers::canonicalize_opaque_pathname, compile_options);
    if (!pathname_component) {
      ada_log("url_pattern_component::compile failed for opaque pathname ",
              processed_init->pathname.value());
      return tl::unexpected(pathname_component.error());
    }
    url_pattern_.pathname_component = std::move(*pathname_component);
  }

  // Set urlPattern's search component to the result of compiling a component
  // given processedInit["search"], canonicalize a search, and compileOptions.
  auto search_component = url_pattern_component<regex_provider>::compile(
      processed_init->search.value(), url_pattern_helpers::canonicalize_search,
      compile_options);
  if (!search_component) {
    ada_log("url_pattern_component::compile failed for search ",
            processed_init->search.value());
    return tl::unexpected(search_component.error());
  }
  url_pattern_.search_component = std::move(*search_component);

  // Set urlPattern's hash component to the result of compiling a component
  // given processedInit["hash"], canonicalize a hash, and compileOptions.
  auto hash_component = url_pattern_component<regex_provider>::compile(
      processed_init->hash.value(), url_pattern_helpers::canonicalize_hash,
      compile_options);
  if (!hash_component) {
    ada_log("url_pattern_component::compile failed for hash ",
            processed_init->hash.value());
    return tl::unexpected(hash_component.error());
  }
  url_pattern_.hash_component = std::move(*hash_component);

  // Return urlPattern.
  return url_pattern_;
}
#endif  // ADA_INCLUDE_URL_PATTERN

}  // namespace ada::parser

#endif  // ADA_PARSER_INL_H
/* end file include/ada/parser-inl.h */
/* begin file include/ada/scheme-inl.h */
/**
 * @file scheme-inl.h
 * @brief Definitions for the URL scheme.
 */
#ifndef ADA_SCHEME_INL_H
#define ADA_SCHEME_INL_H


namespace ada::scheme {

/**
 * @namespace ada::scheme::details
 * @brief Includes the definitions for scheme specific entities
 */
namespace details {
// for use with is_special and get_special_port
// Spaces, if present, are removed from URL.
constexpr std::string_view is_special_list[] = {"http", " ",   "https", "ws",
                                                "ftp",  "wss", "file",  " "};
// for use with get_special_port
constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0};

// @private
// convert a string_view to a 64-bit integer key for fast comparison
constexpr uint64_t make_key(std::string_view sv) {
  uint64_t val = 0;
  for (size_t i = 0; i < sv.size(); i++)
    val |= (uint64_t)(uint8_t)sv[i] << (i * 8);
  return val;
}
// precomputed keys for the special schemes, indexed by a hash of the input
// string
constexpr uint64_t scheme_keys[] = {
    make_key("http"),   // 0: HTTP
    0,                  // 1: sentinel
    make_key("https"),  // 2: HTTPS
    make_key("ws"),     // 3: WS
    make_key("ftp"),    // 4: FTP
    make_key("wss"),    // 5: WSS
    make_key("file"),   // 6: FILE
    0,                  // 7: sentinel
};

// @private
// branchless load of up to 5 characters into a uint64_t, padding with zeros if
// n < 5
inline uint64_t branchless_load5(const char* p, size_t n) {
  uint64_t input = (uint8_t)p[0];
  input |= ((uint64_t)(uint8_t)p[n > 1] << 8) & (0 - (uint64_t)(n > 1));
  input |= ((uint64_t)(uint8_t)p[(n > 2) * 2] << 16) & (0 - (uint64_t)(n > 2));
  input |= ((uint64_t)(uint8_t)p[(n > 3) * 3] << 24) & (0 - (uint64_t)(n > 3));
  input |= ((uint64_t)(uint8_t)p[(n > 4) * 4] << 32) & (0 - (uint64_t)(n > 4));
  return input;
}
}  // namespace details

/****
 * @private
 * In is_special, get_scheme_type, and get_special_port, we
 * use a standard hashing technique to find the index of the scheme in
 * the is_special_list. The hashing technique is based on the size of
 * the scheme and the first character of the scheme. It ensures that we
 * do at most one string comparison per call. If the protocol is
 * predictible (e.g., it is always "http"), we can get a better average
 * performance by using a simpler approach where we loop and compare
 * scheme with all possible protocols starting with the most likely
 * protocol. Doing multiple comparisons may have a poor worst case
 * performance, however. In this instance, we choose a potentially
 * slightly lower best-case performance for a better worst-case
 * performance. We can revisit this choice at any time.
 *
 * Reference:
 * Schmidt, Douglas C. "Gperf: A perfect hash function generator."
 * More C++ gems 17 (2000).
 *
 * Reference: https://en.wikipedia.org/wiki/Perfect_hash_function
 *
 * Reference: https://github.com/ada-url/ada/issues/617
 ****/

ada_really_inline constexpr bool is_special(std::string_view scheme) {
  if (scheme.empty()) {
    return false;
  }
  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
  const std::string_view target = details::is_special_list[hash_value];
  return (target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1));
}
constexpr uint16_t get_special_port(std::string_view scheme) noexcept {
  if (scheme.empty()) {
    return 0;
  }
  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
  const std::string_view target = details::is_special_list[hash_value];
  if (scheme.size() == target.size() &&
      details::branchless_load5(scheme.data(), scheme.size()) ==
          details::scheme_keys[hash_value]) {
    return details::special_ports[hash_value];
  } else {
    return 0;
  }
}
constexpr uint16_t get_special_port(ada::scheme::type type) noexcept {
  return details::special_ports[int(type)];
}
constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept {
  if (scheme.empty()) {
    return ada::scheme::NOT_SPECIAL;
  }
  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
  const std::string_view target = details::is_special_list[hash_value];
  if (scheme.size() == target.size() &&
      details::branchless_load5(scheme.data(), scheme.size()) ==
          details::scheme_keys[hash_value]) {
    return ada::scheme::type(hash_value);
  } else {
    return ada::scheme::NOT_SPECIAL;
  }
}

}  // namespace ada::scheme

#endif  // ADA_SCHEME_INL_H
/* end file include/ada/scheme-inl.h */
/* begin file include/ada/serializers.h */
/**
 * @file serializers.h
 * @brief IP address serialization utilities.
 *
 * This header provides functions for converting IP addresses to their
 * string representations according to the WHATWG URL Standard.
 */
#ifndef ADA_SERIALIZERS_H
#define ADA_SERIALIZERS_H


#include <array>
#include <string>

/**
 * @namespace ada::serializers
 * @brief IP address serialization functions.
 *
 * Contains utilities for serializing IPv4 and IPv6 addresses to strings.
 */
namespace ada::serializers {

/**
 * Finds the longest consecutive sequence of zero pieces in an IPv6 address.
 * Used for :: compression in IPv6 serialization.
 *
 * @param address The 8 16-bit pieces of the IPv6 address.
 * @param[out] compress Index of the start of the longest zero sequence.
 * @param[out] compress_length Length of the longest zero sequence.
 */
void find_longest_sequence_of_ipv6_pieces(
    const std::array<uint16_t, 8>& address, size_t& compress,
    size_t& compress_length) noexcept;

/**
 * Serializes an IPv6 address to its string representation.
 *
 * @param address The 8 16-bit pieces of the IPv6 address.
 * @return The serialized IPv6 string (e.g., "2001:db8::1").
 * @see https://url.spec.whatwg.org/#concept-ipv6-serializer
 */
std::string ipv6(const std::array<uint16_t, 8>& address);

/**
 * Serializes an IPv4 address to its dotted-decimal string representation.
 *
 * @param address The 32-bit IPv4 address as an integer.
 * @return The serialized IPv4 string (e.g., "192.168.1.1").
 * @see https://url.spec.whatwg.org/#concept-ipv4-serializer
 */
std::string ipv4(uint64_t address);

}  // namespace ada::serializers

#endif  // ADA_SERIALIZERS_H
/* end file include/ada/serializers.h */
/* begin file include/ada/state.h */
/**
 * @file state.h
 * @brief URL parser state machine states.
 *
 * Defines the states used by the URL parsing state machine as specified
 * in the WHATWG URL Standard.
 *
 * @see https://url.spec.whatwg.org/#url-parsing
 */
#ifndef ADA_STATE_H
#define ADA_STATE_H


#include <string>

namespace ada {

/**
 * @brief States in the URL parsing state machine.
 *
 * The URL parser processes input through a sequence of states, each handling
 * a specific part of the URL syntax.
 *
 * @see https://url.spec.whatwg.org/#url-parsing
 */
enum class state {
  /**
   * @see https://url.spec.whatwg.org/#authority-state
   */
  AUTHORITY,

  /**
   * @see https://url.spec.whatwg.org/#scheme-start-state
   */
  SCHEME_START,

  /**
   * @see https://url.spec.whatwg.org/#scheme-state
   */
  SCHEME,

  /**
   * @see https://url.spec.whatwg.org/#host-state
   */
  HOST,

  /**
   * @see https://url.spec.whatwg.org/#no-scheme-state
   */
  NO_SCHEME,

  /**
   * @see https://url.spec.whatwg.org/#fragment-state
   */
  FRAGMENT,

  /**
   * @see https://url.spec.whatwg.org/#relative-state
   */
  RELATIVE_SCHEME,

  /**
   * @see https://url.spec.whatwg.org/#relative-slash-state
   */
  RELATIVE_SLASH,

  /**
   * @see https://url.spec.whatwg.org/#file-state
   */
  FILE,

  /**
   * @see https://url.spec.whatwg.org/#file-host-state
   */
  FILE_HOST,

  /**
   * @see https://url.spec.whatwg.org/#file-slash-state
   */
  FILE_SLASH,

  /**
   * @see https://url.spec.whatwg.org/#path-or-authority-state
   */
  PATH_OR_AUTHORITY,

  /**
   * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state
   */
  SPECIAL_AUTHORITY_IGNORE_SLASHES,

  /**
   * @see https://url.spec.whatwg.org/#special-authority-slashes-state
   */
  SPECIAL_AUTHORITY_SLASHES,

  /**
   * @see https://url.spec.whatwg.org/#special-relative-or-authority-state
   */
  SPECIAL_RELATIVE_OR_AUTHORITY,

  /**
   * @see https://url.spec.whatwg.org/#query-state
   */
  QUERY,

  /**
   * @see https://url.spec.whatwg.org/#path-state
   */
  PATH,

  /**
   * @see https://url.spec.whatwg.org/#path-start-state
   */
  PATH_START,

  /**
   * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state
   */
  OPAQUE_PATH,

  /**
   * @see https://url.spec.whatwg.org/#port-state
   */
  PORT,
};

/**
 * Converts a parser state to its string name for debugging.
 * @param s The state to convert.
 * @return A string representation of the state.
 */
ada_warn_unused std::string to_string(ada::state s);

}  // namespace ada

#endif  // ADA_STATE_H
/* end file include/ada/state.h */
/* begin file include/ada/unicode.h */
/**
 * @file unicode.h
 * @brief Definitions for all unicode specific functions.
 */
#ifndef ADA_UNICODE_H
#define ADA_UNICODE_H


#include <string>
#include <string_view>
#include <optional>

/**
 * Unicode operations. These functions are not part of our public API and may
 * change at any time.
 *
 * @private
 * @namespace ada::unicode
 * @brief Includes the definitions for unicode operations
 */
namespace ada::unicode {

/**
 * @private
 * We receive a UTF-8 string representing a domain name.
 * If the string is percent encoded, we apply percent decoding.
 *
 * Given a domain, we need to identify its labels.
 * They are separated by label-separators:
 *
 * U+002E (.) FULL STOP
 * U+FF0E FULLWIDTH FULL STOP
 * U+3002 IDEOGRAPHIC FULL STOP
 * U+FF61 HALFWIDTH IDEOGRAPHIC FULL STOP
 *
 * They are all mapped to U+002E.
 *
 * We process each label into a string that should not exceed 63 octets.
 * If the string is already punycode (starts with "xn--"), then we must
 * scan it to look for unallowed code points.
 * Otherwise, if the string is not pure ASCII, we need to transcode it
 * to punycode by following RFC 3454 which requires us to
 * - Map characters  (see section 3),
 * - Normalize (see section 4),
 * - Reject forbidden characters,
 * - Check for right-to-left characters and if so, check all requirements (see
 * section 6),
 * - Optionally reject based on unassigned code points (section 7).
 *
 * The Unicode standard provides a table of code points with a mapping, a list
 * of forbidden code points and so forth. This table is subject to change and
 * will vary based on the implementation. For Unicode 15, the table is at
 * https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt
 * If you use ICU, they parse this table and map it to code using a Python
 * script.
 *
 * The resulting strings should not exceed 255 octets according to RFC 1035
 * section 2.3.4. ICU checks for label size and domain size, but these errors
 * are ignored.
 *
 * @see https://url.spec.whatwg.org/#concept-domain-to-ascii
 *
 */
bool to_ascii(std::optional<std::string>& out, std::string_view plain,
              size_t first_percent);

/**
 * @private
 * Checks if the input has tab or newline characters.
 *
 * @attention The has_tabs_or_newline function is a bottleneck and it is simple
 * enough that compilers like GCC can 'autovectorize it'.
 */
ada_really_inline bool has_tabs_or_newline(
    std::string_view user_input) noexcept;

/**
 * @private
 * Checks if the input is a forbidden host code point.
 * @see https://url.spec.whatwg.org/#forbidden-host-code-point
 */
ada_really_inline constexpr bool is_forbidden_host_code_point(char c) noexcept;

/**
 * @private
 * Checks if the input contains a forbidden domain code point.
 * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
 */
ada_really_inline constexpr bool contains_forbidden_domain_code_point(
    const char* input, size_t length) noexcept;

/**
 * @private
 * Checks if the input contains a forbidden domain code point in which case
 * the first bit is set to 1. If the input contains an upper case ASCII letter,
 * then the second bit is set to 1.
 * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
 */
ada_really_inline constexpr uint8_t
contains_forbidden_domain_code_point_or_upper(const char* input,
                                              size_t length) noexcept;

/**
 * @private
 * Checks if the input is a forbidden domain code point.
 * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
 */
ada_really_inline constexpr bool is_forbidden_domain_code_point(
    char c) noexcept;

/**
 * @private
 * Checks if the input is alphanumeric, '+', '-' or '.'
 */
ada_really_inline constexpr bool is_alnum_plus(char c) noexcept;

/**
 * @private
 * @details An ASCII hex digit is an ASCII upper hex digit or ASCII lower hex
 * digit. An ASCII upper hex digit is an ASCII digit or a code point in the
 * range U+0041 (A) to U+0046 (F), inclusive. An ASCII lower hex digit is an
 * ASCII digit or a code point in the range U+0061 (a) to U+0066 (f), inclusive.
 */
ada_really_inline constexpr bool is_ascii_hex_digit(char c) noexcept;

/**
 * @private
 * An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9),
 * inclusive.
 */
ada_really_inline constexpr bool is_ascii_digit(char c) noexcept;

/**
 * @private
 * @details If a char is between U+0000 and U+007F inclusive, then it's an ASCII
 * character.
 */
ada_really_inline constexpr bool is_ascii(char32_t c) noexcept;

/**
 * @private
 * Checks if the input is a C0 control or space character.
 *
 * @details A C0 control or space is a C0 control or U+0020 SPACE.
 * A C0 control is a code point in the range U+0000 NULL to U+001F INFORMATION
 * SEPARATOR ONE, inclusive.
 */
ada_really_inline constexpr bool is_c0_control_or_space(char c) noexcept;

/**
 * @private
 * Checks if the input is a ASCII tab or newline character.
 *
 * @details An ASCII tab or newline is U+0009 TAB, U+000A LF, or U+000D CR.
 */
ada_really_inline constexpr bool is_ascii_tab_or_newline(char c) noexcept;

/**
 * @private
 * @details A double-dot path segment must be ".." or an ASCII case-insensitive
 * match for ".%2e", "%2e.", or "%2e%2e".
 */
ada_really_inline constexpr bool is_double_dot_path_segment(
    std::string_view input) noexcept;

/**
 * @private
 * @details A single-dot path segment must be "." or an ASCII case-insensitive
 * match for "%2e".
 */
ada_really_inline constexpr bool is_single_dot_path_segment(
    std::string_view input) noexcept;

/**
 * @private
 * @details ipv4 character might contain 0-9 or a-f character ranges.
 */
ada_really_inline constexpr bool is_lowercase_hex(char c) noexcept;

/**
 * @private
 * @details Convert hex to binary. Caller is responsible to ensure that
 * the parameter is an hexadecimal digit (0-9, A-F, a-f).
 */
ada_really_inline unsigned constexpr convert_hex_to_binary(char c) noexcept;

/**
 * @private
 * first_percent should be  = input.find('%')
 *
 * @todo It would be faster as noexcept maybe, but it could be unsafe since.
 * @author Node.js
 * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L245
 * @see https://encoding.spec.whatwg.org/#utf-8-decode-without-bom
 */
std::string percent_decode(std::string_view input, size_t first_percent);

/**
 * Decode an application/x-www-form-urlencoded component: map '+' to space,
 * then percent-decode. Single allocation; no intermediate string.
 *
 * @param input A form-urlencoded component (key or value).
 * @return The decoded string.
 * @see https://url.spec.whatwg.org/#concept-urlencoded-parser
 */
std::string form_urlencoded_decode(std::string_view input);

/**
 * @private
 * Returns a percent-encoding string whether percent encoding was needed or not.
 * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
 */
std::string percent_encode(std::string_view input,
                           const uint8_t character_set[]);
/**
 * @private
 * Returns a percent-encoded string version of input, while starting the percent
 * encoding at the provided index.
 * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
 */
std::string percent_encode(std::string_view input,
                           const uint8_t character_set[], size_t index);
/**
 * @private
 * Returns true if percent encoding was needed, in which case, we store
 * the percent-encoded content in 'out'. If the boolean 'append' is set to
 * true, the content is appended to 'out'.
 * If percent encoding is not needed, out is left unchanged.
 * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
 */
template <bool append>
bool percent_encode(std::string_view input, const uint8_t character_set[],
                    std::string& out);
/**
 * @private
 * Returns the index at which percent encoding should start, or (equivalently),
 * the length of the prefix that does not require percent encoding.
 */
ada_really_inline size_t percent_encode_index(std::string_view input,
                                              const uint8_t character_set[]);
/**
 * @private
 * Lowers the string in-place, assuming that the content is ASCII.
 * Return true if the content was ASCII.
 */
constexpr bool to_lower_ascii(char* input, size_t length) noexcept;
}  // namespace ada::unicode

#endif  // ADA_UNICODE_H
/* end file include/ada/unicode.h */
/* begin file include/ada/url_base-inl.h */
/**
 * @file url_base-inl.h
 * @brief Inline functions for url base
 */
#ifndef ADA_URL_BASE_INL_H
#define ADA_URL_BASE_INL_H


#include <string>
#if ADA_REGULAR_VISUAL_STUDIO
#include <intrin.h>
#endif  // ADA_REGULAR_VISUAL_STUDIO

namespace ada {

[[nodiscard]] ada_really_inline constexpr bool url_base::is_special()
    const noexcept {
  return type != ada::scheme::NOT_SPECIAL;
}

[[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept {
  return ada::scheme::get_special_port(type);
}

[[nodiscard]] ada_really_inline uint16_t
url_base::scheme_default_port() const noexcept {
  return scheme::get_special_port(type);
}

}  // namespace ada

#endif  // ADA_URL_BASE_INL_H
/* end file include/ada/url_base-inl.h */
/* begin file include/ada/url-inl.h */
/**
 * @file url-inl.h
 * @brief Definitions for the URL
 */
#ifndef ADA_URL_INL_H
#define ADA_URL_INL_H


#include <charconv>
#include <cstring>
#include <optional>
#include <string>
#if ADA_REGULAR_VISUAL_STUDIO
#include <intrin.h>
#endif  // ADA_REGULAR_VISUAL_STUDIO

namespace ada {
[[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept {
  return !username.empty() || !password.empty();
}
[[nodiscard]] ada_really_inline bool url::has_port() const noexcept {
  return port.has_value();
}
[[nodiscard]] inline bool url::cannot_have_credentials_or_port() const {
  return !host.has_value() || host->empty() || type == ada::scheme::type::FILE;
}
[[nodiscard]] inline bool url::has_empty_hostname() const noexcept {
  if (!host.has_value()) {
    return false;
  }
  return host->empty();
}
[[nodiscard]] inline bool url::has_hostname() const noexcept {
  return host.has_value();
}
inline std::ostream& operator<<(std::ostream& out, const ada::url& u) {
  return out << u.to_string();
}

[[nodiscard]] size_t url::get_pathname_length() const noexcept {
  return path.size();
}

[[nodiscard]] constexpr std::string_view url::get_pathname() const noexcept {
  return path;
}

[[nodiscard]] ada_really_inline ada::url_components url::get_components()
    const {
  url_components out{};

  // protocol ends with ':'. for example: "https:"
  out.protocol_end = uint32_t(get_protocol().size());

  // Trailing index is always the next character of the current one.
  // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
  size_t running_index = out.protocol_end;

  if (host.has_value()) {
    // 2 characters for "//" and 1 character for starting index
    out.host_start = out.protocol_end + 2;

    if (has_credentials()) {
      out.username_end = uint32_t(out.host_start + username.size());

      out.host_start += uint32_t(username.size());

      if (!password.empty()) {
        out.host_start += uint32_t(password.size() + 1);
      }

      out.host_end = uint32_t(out.host_start + host->size());
    } else {
      out.username_end = out.host_start;

      // Host does not start with "@" if it does not include credentials.
      out.host_end = uint32_t(out.host_start + host->size()) - 1;
    }

    running_index = out.host_end + 1;
  } else {
    // Update host start and end date to the same index, since it does not
    // exist.
    out.host_start = out.protocol_end;
    out.host_end = out.host_start;

    if (!has_opaque_path && path.starts_with("//")) {
      // If url's host is null, url does not have an opaque path, url's path's
      // size is greater than 1, and url's path[0] is the empty string, then
      // append U+002F (/) followed by U+002E (.) to output.
      running_index = out.protocol_end + 2;
    } else {
      running_index = out.protocol_end;
    }
  }

  if (port.has_value()) {
    out.port = *port;
    running_index += helpers::fast_digit_count(*port) + 1;  // Port omits ':'
  }

  out.pathname_start = uint32_t(running_index);

  running_index += path.size();

  if (query.has_value()) {
    out.search_start = uint32_t(running_index);
    running_index += get_search().size();
    if (get_search().empty()) {
      running_index++;
    }
  }

  if (hash.has_value()) {
    out.hash_start = uint32_t(running_index);
  }

  return out;
}

inline void url::update_base_hostname(std::string_view input) { host = input; }

inline void url::update_unencoded_base_hash(std::string_view input) {
  // We do the percent encoding
  hash = unicode::percent_encode(input,
                                 ada::character_sets::FRAGMENT_PERCENT_ENCODE);
}

inline void url::update_base_search(std::string_view input,
                                    const uint8_t query_percent_encode_set[]) {
  query = ada::unicode::percent_encode(input, query_percent_encode_set);
}

inline void url::update_base_search(std::optional<std::string>&& input) {
  query = std::move(input);
}

inline void url::update_base_pathname(const std::string_view input) {
  path = input;
}

inline void url::update_base_username(const std::string_view input) {
  username = input;
}

inline void url::update_base_password(const std::string_view input) {
  password = input;
}

inline void url::update_base_port(std::optional<uint16_t> input) {
  port = input;
}

constexpr void url::clear_pathname() { path.clear(); }

constexpr void url::clear_search() { query = std::nullopt; }

[[nodiscard]] constexpr bool url::has_hash() const noexcept {
  return hash.has_value();
}

[[nodiscard]] constexpr bool url::has_search() const noexcept {
  return query.has_value();
}

constexpr void url::set_protocol_as_file() { type = ada::scheme::type::FILE; }

inline void url::set_scheme(std::string&& new_scheme) noexcept {
  type = ada::scheme::get_scheme_type(new_scheme);
  // We only move the 'scheme' if it is non-special.
  if (!is_special()) {
    non_special_scheme = std::move(new_scheme);
  }
}

constexpr void url::copy_scheme(ada::url&& u) {
  non_special_scheme = u.non_special_scheme;
  type = u.type;
}

constexpr void url::copy_scheme(const ada::url& u) {
  non_special_scheme = u.non_special_scheme;
  type = u.type;
}

[[nodiscard]] ada_really_inline std::string url::get_href() const {
  if (is_special() && host.has_value() && username.empty() &&
      password.empty() && !port.has_value()) [[likely]] {
    const std::string_view scheme = ada::scheme::details::is_special_list[type];
    const size_t host_size = host->size();
    const size_t path_size = path.size();
    const size_t query_size = query.has_value() ? query->size() : 0;
    const size_t hash_size = hash.has_value() ? hash->size() : 0;
    const size_t total = scheme.size() + 3 + host_size + path_size +
                         (query.has_value() ? query_size + 1 : 0) +
                         (hash.has_value() ? hash_size + 1 : 0);
    std::string output(total, '\0');
    char* p = output.data();
    std::memcpy(p, scheme.data(), scheme.size());
    p += scheme.size();
    p[0] = ':';
    p[1] = '/';
    p[2] = '/';
    p += 3;
    // NOLINTNEXTLINE(bugprone-not-null-terminated-result)
    std::memcpy(p, host->data(), host_size);
    p += host_size;
    std::memcpy(p, path.data(), path_size);
    p += path_size;
    if (query.has_value()) {
      *p++ = '?';
      // NOLINTNEXTLINE(bugprone-not-null-terminated-result)
      std::memcpy(p, query->data(), query_size);
      p += query_size;
    }
    if (hash.has_value()) {
      *p++ = '#';
      // NOLINTNEXTLINE(bugprone-not-null-terminated-result)
      std::memcpy(p, hash->data(), hash_size);
    }
    return output;
  }

  std::string output;
  output.reserve(get_href_size());

  if (is_special()) {
    output.append(ada::scheme::details::is_special_list[type]);
    output += ':';
  } else {
    output.append(non_special_scheme);
    output += ':';
  }

  if (host.has_value()) {
    output += '/';
    output += '/';
    if (has_credentials()) {
      output.append(username);
      if (!password.empty()) {
        output += ':';
        output.append(password);
      }
      output += '@';
    }
    output.append(*host);
    if (port.has_value()) {
      output += ':';
      char port_buf[5];
      auto [ptr, ec] = std::to_chars(port_buf, port_buf + 5, *port);
      (void)ec;
      output.append(port_buf, static_cast<size_t>(ptr - port_buf));
    }
  } else if (!has_opaque_path && path.starts_with("//")) {
    // If url's host is null, url does not have an opaque path, url's path's
    // size is greater than 1, and url's path[0] is the empty string, then
    // append U+002F (/) followed by U+002E (.) to output.
    output += '/';
    output += '.';
  }
  output.append(path);
  if (query.has_value()) {
    output += '?';
    output.append(*query);
  }
  if (hash.has_value()) {
    output += '#';
    output.append(*hash);
  }
  return output;
}

[[nodiscard]] inline size_t url::get_href_size() const noexcept {
  size_t size = 0;
  if (is_special()) {
    size += ada::scheme::details::is_special_list[type].size() + 1;
  } else {
    size += non_special_scheme.size() + 1;
  }
  if (host.has_value()) {
    size += host->size();
    size += 2;
    if (has_credentials()) {
      size += username.size();
      if (!password.empty()) {
        size += 1 + password.size();
      }
      size += 1;
    }
    if (port.has_value()) {
      size += 1;
      uint16_t p = *port;
      size += (p >= 10000)  ? 5
              : (p >= 1000) ? 4
              : (p >= 100)  ? 3
              : (p >= 10)   ? 2
                            : 1;
    }
  } else if (!has_opaque_path && path.starts_with("//")) {
    size += 2;
  }
  size += path.size();
  if (query.has_value()) {
    size += 1 + query->size();
  }
  if (hash.has_value()) {
    size += 1 + hash->size();
  }
  return size;
}

ada_really_inline size_t url::parse_port(std::string_view view,
                                         bool check_trailing_content) noexcept {
  ada_log("parse_port('", view, "') ", view.size());
  if (!view.empty() && view[0] == '-') {
    ada_log("parse_port: view[0] == '0' && view.size() > 1");
    is_valid = false;
    return 0;
  }
  uint16_t parsed_port{};
  auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port);
  if (r.ec == std::errc::result_out_of_range) {
    ada_log("parse_port: r.ec == std::errc::result_out_of_range");
    is_valid = false;
    return 0;
  }
  ada_log("parse_port: ", parsed_port);
  const auto consumed = size_t(r.ptr - view.data());
  ada_log("parse_port: consumed ", consumed);
  if (check_trailing_content) {
    is_valid &=
        (consumed == view.size() || view[consumed] == '/' ||
         view[consumed] == '?' || (is_special() && view[consumed] == '\\'));
  }
  ada_log("parse_port: is_valid = ", is_valid);
  if (is_valid) {
    // scheme_default_port can return 0, and we should allow 0 as a base port.
    auto default_port = scheme_default_port();
    bool is_port_valid = (default_port == 0 && parsed_port == 0) ||
                         (default_port != parsed_port);
    port = (r.ec == std::errc() && is_port_valid) ? std::optional(parsed_port)
                                                  : std::nullopt;
  }
  return consumed;
}

}  // namespace ada

#endif  // ADA_URL_H
/* end file include/ada/url-inl.h */
/* begin file include/ada/url_components-inl.h */
/**
 * @file url_components.h
 * @brief Declaration for the URL Components
 */
#ifndef ADA_URL_COMPONENTS_INL_H
#define ADA_URL_COMPONENTS_INL_H


namespace ada {

[[nodiscard]] constexpr bool url_components::check_offset_consistency()
    const noexcept {
  /**
   * https://user:pass@example.com:1234/foo/bar?baz#quux
   *       |     |    |          | ^^^^|       |   |
   *       |     |    |          | |   |       |   `----- hash_start
   *       |     |    |          | |   |       `--------- search_start
   *       |     |    |          | |   `----------------- pathname_start
   *       |     |    |          | `--------------------- port
   *       |     |    |          `----------------------- host_end
   *       |     |    `---------------------------------- host_start
   *       |     `--------------------------------------- username_end
   *       `--------------------------------------------- protocol_end
   */
  // These conditions can be made more strict.
  if (protocol_end == url_components::omitted) {
    return false;
  }
  uint32_t index = protocol_end;

  if (username_end == url_components::omitted) {
    return false;
  }
  if (username_end < index) {
    return false;
  }
  index = username_end;

  if (host_start == url_components::omitted) {
    return false;
  }
  if (host_start < index) {
    return false;
  }
  index = host_start;

  if (port != url_components::omitted) {
    if (port > 0xffff) {
      return false;
    }
    uint32_t port_length = helpers::fast_digit_count(port) + 1;
    if (index + port_length < index) {
      return false;
    }
    index += port_length;
  }

  if (pathname_start == url_components::omitted) {
    return false;
  }
  if (pathname_start < index) {
    return false;
  }
  index = pathname_start;

  if (search_start != url_components::omitted) {
    if (search_start < index) {
      return false;
    }
    index = search_start;
  }

  if (hash_start != url_components::omitted) {
    if (hash_start < index) {
      return false;
    }
  }

  return true;
}

}  // namespace ada
#endif
/* end file include/ada/url_components-inl.h */
/* begin file include/ada/url_aggregator.h */
/**
 * @file url_aggregator.h
 * @brief Declaration for the `ada::url_aggregator` class.
 *
 * This file contains the `ada::url_aggregator` struct which represents a parsed
 * URL using a single buffer with component offsets. This is the default and
 * most memory-efficient URL representation in Ada.
 *
 * @see url.h for an alternative representation using separate strings
 */
#ifndef ADA_URL_AGGREGATOR_H
#define ADA_URL_AGGREGATOR_H

#include <ostream>
#include <string>
#include <string_view>
#include <variant>


namespace ada {

namespace parser {}

/**
 * @brief Memory-efficient URL representation using a single buffer.
 *
 * The `url_aggregator` stores the entire normalized URL in a single string
 * buffer and tracks component boundaries using offsets. This design minimizes
 * memory allocations and is ideal for read-mostly access patterns.
 *
 * Getter methods return `std::string_view` pointing into the internal buffer.
 * These views are lightweight (no allocation) but become invalid if the
 * url_aggregator is modified or destroyed.
 *
 * @warning Views returned by getters (e.g., `get_pathname()`) are invalidated
 * when any setter is called. Do not use a getter's result as input to a
 * setter on the same object without copying first.
 *
 * @note This is the default URL type returned by `ada::parse()`.
 *
 * @see url For an alternative using separate std::string instances
 */
struct url_aggregator : url_base {
  url_aggregator() = default;
  url_aggregator(const url_aggregator& u) = default;
  url_aggregator(url_aggregator&& u) noexcept = default;
  url_aggregator& operator=(url_aggregator&& u) noexcept = default;
  url_aggregator& operator=(const url_aggregator& u) = default;
  ~url_aggregator() override = default;

  /**
   * The setter functions follow the steps defined in the URL Standard.
   *
   * The url_aggregator has a single buffer that contains the entire normalized
   * URL. The various components are represented as offsets into that buffer.
   * When you call get_pathname(), for example, you get a std::string_view that
   * points into that buffer. If the url_aggregator is modified, the buffer may
   * be reallocated, and the std::string_view you obtained earlier may become
   * invalid. In particular, this implies that you cannot modify the URL using
   * a setter function with a std::string_view that points into the
   * url_aggregator E.g., the following is incorrect:
   * url->set_hostname(url->get_pathname()).
   * You must first copy the pathname to a separate string.
   * std::string pathname(url->get_pathname());
   * url->set_hostname(pathname);
   *
   * The caller is responsible for ensuring that the url_aggregator is not
   * modified while any std::string_view obtained from it is in use.
   */
  bool set_href(std::string_view input);
  bool set_host(std::string_view input);
  bool set_hostname(std::string_view input);
  bool set_protocol(std::string_view input);
  bool set_username(std::string_view input);
  bool set_password(std::string_view input);
  bool set_port(std::string_view input);
  bool set_pathname(std::string_view input);
  void set_search(std::string_view input);
  void set_hash(std::string_view input);

  /**
   * Validates whether the hostname is a valid domain according to RFC 1034.
   * @return `true` if the domain is valid, `false` otherwise.
   */
  [[nodiscard]] bool has_valid_domain() const noexcept override;

  /**
   * Returns the URL's origin (scheme + host + port for special URLs).
   * @return A newly allocated string containing the serialized origin.
   * @see https://url.spec.whatwg.org/#concept-url-origin
   */
  [[nodiscard]] std::string get_origin() const override;

  /**
   * Returns the full serialized URL (the href) as a string_view.
   * Does not allocate memory. The returned view becomes invalid if this
   * url_aggregator is modified or destroyed.
   * @return A string_view into the internal buffer.
   * @see https://url.spec.whatwg.org/#dom-url-href
   */
  [[nodiscard]] constexpr std::string_view get_href() const noexcept
      ada_lifetime_bound;

  /**
   * Returns the byte length of the serialized URL without allocating a string.
   * @return Size of the href in bytes.
   */
  [[nodiscard]] constexpr size_t get_href_size() const noexcept;

  /**
   * Returns the URL's username component.
   * Does not allocate memory. The returned view becomes invalid if this
   * url_aggregator is modified or destroyed.
   * @return A string_view of the username.
   * @see https://url.spec.whatwg.org/#dom-url-username
   */
  [[nodiscard]] std::string_view get_username() const ada_lifetime_bound;

  /**
   * Returns the URL's password component.
   * Does not allocate memory. The returned view becomes invalid if this
   * url_aggregator is modified or destroyed.
   * @return A string_view of the password.
   * @see https://url.spec.whatwg.org/#dom-url-password
   */
  [[nodiscard]] std::string_view get_password() const ada_lifetime_bound;

  /**
   * Returns the URL's port as a string (e.g., "8080").
   * Does not allocate memory. Returns empty view if no port is set.
   * The returned view becomes invalid if this url_aggregator is modified.
   * @return A string_view of the port.
   * @see https://url.spec.whatwg.org/#dom-url-port
   */
  [[nodiscard]] std::string_view get_port() const ada_lifetime_bound;

  /**
   * Returns the URL's fragment prefixed with '#' (e.g., "#section").
   * Does not allocate memory. Returns empty view if no fragment is set.
   * The returned view becomes invalid if this url_aggregator is modified.
   * @return A string_view of the hash.
   * @see https://url.spec.whatwg.org/#dom-url-hash
   */
  [[nodiscard]] std::string_view get_hash() const ada_lifetime_bound;

  /**
   * Returns the URL's host and port (e.g., "example.com:8080").
   * Does not allocate memory. Returns empty view if no host is set.
   * The returned view becomes invalid if this url_aggregator is modified.
   * @return A string_view of host:port.
   * @see https://url.spec.whatwg.org/#dom-url-host
   */
  [[nodiscard]] std::string_view get_host() const ada_lifetime_bound;

  /**
   * Returns the URL's hostname (without port).
   * Does not allocate memory. Returns empty view if no host is set.
   * The returned view becomes invalid if this url_aggregator is modified.
   * @return A string_view of the hostname.
   * @see https://url.spec.whatwg.org/#dom-url-hostname
   */
  [[nodiscard]] std::string_view get_hostname() const ada_lifetime_bound;

  /**
   * Returns the URL's path component.
   * Does not allocate memory. The returned view becomes invalid if this
   * url_aggregator is modified or destroyed.
   * @return A string_view of the pathname.
   * @see https://url.spec.whatwg.org/#dom-url-pathname
   */
  [[nodiscard]] constexpr std::string_view get_pathname() const
      ada_lifetime_bound;

  /**
   * Returns the byte length of the pathname without creating a string.
   * @return Size of the pathname in bytes.
   * @see https://url.spec.whatwg.org/#dom-url-pathname
   */
  [[nodiscard]] ada_really_inline uint32_t get_pathname_length() const noexcept;

  /**
   * Returns the URL's query string prefixed with '?' (e.g., "?foo=bar").
   * Does not allocate memory. Returns empty view if no query is set.
   * The returned view becomes invalid if this url_aggregator is modified.
   * @return A string_view of the search/query.
   * @see https://url.spec.whatwg.org/#dom-url-search
   */
  [[nodiscard]] std::string_view get_search() const ada_lifetime_bound;

  /**
   * Returns the URL's scheme followed by a colon (e.g., "https:").
   * Does not allocate memory. The returned view becomes invalid if this
   * url_aggregator is modified or destroyed.
   * @return A string_view of the protocol.
   * @see https://url.spec.whatwg.org/#dom-url-protocol
   */
  [[nodiscard]] std::string_view get_protocol() const ada_lifetime_bound;

  /**
   * Checks if the URL has credentials (non-empty username or password).
   * @return `true` if username or password is non-empty, `false` otherwise.
   */
  [[nodiscard]] ada_really_inline constexpr bool has_credentials()
      const noexcept;

  /**
   * Returns the URL component offsets for efficient serialization.
   *
   * The components represent byte offsets into the serialized URL:
   * ```
   * https://user:pass@example.com:1234/foo/bar?baz#quux
   *       |     |    |          | ^^^^|       |   |
   *       |     |    |          | |   |       |   `----- hash_start
   *       |     |    |          | |   |       `--------- search_start
   *       |     |    |          | |   `----------------- pathname_start
   *       |     |    |          | `--------------------- port
   *       |     |    |          `----------------------- host_end
   *       |     |    `---------------------------------- host_start
   *       |     `--------------------------------------- username_end
   *       `--------------------------------------------- protocol_end
   * ```
   * @return A constant reference to the url_components struct.
   * @see https://github.com/servo/rust-url
   */
  [[nodiscard]] ada_really_inline const url_components& get_components()
      const noexcept;

  /**
   * Returns a JSON string representation of this URL for debugging.
   * @return A JSON-formatted string with all URL components.
   */
  [[nodiscard]] std::string to_string() const override;

  /**
   * Returns a visual diagram showing component boundaries in the URL.
   * Useful for debugging and understanding URL structure.
   * @return A multi-line string diagram.
   */
  [[nodiscard]] std::string to_diagram() const;

  /**
   * Validates internal consistency of component offsets (for debugging).
   * @return `true` if offsets are consistent, `false` if corrupted.
   */
  [[nodiscard]] constexpr bool validate() const noexcept;

  /**
   * Checks if the URL has an empty hostname (host is set but empty string).
   * @return `true` if host exists but is empty, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_empty_hostname() const noexcept;

  /**
   * Checks if the URL has a hostname (including empty hostnames).
   * @return `true` if host is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_hostname() const noexcept;

  /**
   * Checks if the URL has a non-empty username.
   * @return `true` if username is non-empty, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_non_empty_username() const noexcept;

  /**
   * Checks if the URL has a non-empty password.
   * @return `true` if password is non-empty, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_non_empty_password() const noexcept;

  /**
   * Checks if the URL has a non-default port explicitly specified.
   * @return `true` if a port is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_port() const noexcept;

  /**
   * Checks if the URL has a password component (may be empty).
   * @return `true` if password is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_password() const noexcept;

  /**
   * Checks if the URL has a fragment/hash component.
   * @return `true` if hash is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_hash() const noexcept override;

  /**
   * Checks if the URL has a query/search component.
   * @return `true` if query is present, `false` otherwise.
   */
  [[nodiscard]] constexpr bool has_search() const noexcept override;

  /**
   * Removes the port from the URL.
   */
  inline void clear_port();

  /**
   * Removes the hash/fragment from the URL.
   */
  inline void clear_hash();

  /**
   * Removes the query/search string from the URL.
   */
  inline void clear_search() override;

 private:
  // helper methods
  friend void helpers::strip_trailing_spaces_from_opaque_path<url_aggregator>(
      url_aggregator& url);
  // parse_url methods
  friend url_aggregator parser::parse_url<url_aggregator>(
      std::string_view, const url_aggregator*);

  friend url_aggregator parser::parse_url_impl<url_aggregator, true>(
      std::string_view, const url_aggregator*);
  friend url_aggregator parser::parse_url_impl<url_aggregator, false>(
      std::string_view, const url_aggregator*);
  template <class result_type>
  friend bool parser::try_parse_simple_absolute(std::string_view, result_type&);

#if ADA_INCLUDE_URL_PATTERN
  // url_pattern methods
  template <url_pattern_regex::regex_concept regex_provider>
  friend tl::expected<url_pattern<regex_provider>, errors>
  parse_url_pattern_impl(
      std::variant<std::string_view, url_pattern_init>&& input,
      const std::string_view* base_url, const url_pattern_options* options);
#endif  // ADA_INCLUDE_URL_PATTERN

  // components is declared before buffer so that the offset fields land
  // close to url_base in memory, improving cache locality for getter calls.
  // Note: exact cache-line placement is implementation- and platform-dependent.
  url_components components{};
  std::string buffer{};

  /**
   * Returns true if neither the search, nor the hash nor the pathname
   * have been set.
   * @return true if the buffer is ready to receive the path.
   */
  [[nodiscard]] ada_really_inline bool is_at_path() const noexcept;

  inline void add_authority_slashes_if_needed();

  /**
   * To optimize performance, you may indicate how much memory to allocate
   * within this instance.
   */
  constexpr void reserve(uint32_t capacity);

  ada_really_inline size_t parse_port(std::string_view view,
                                      bool check_trailing_content) override;

  ada_really_inline size_t parse_port(std::string_view view) override {
    return this->parse_port(view, false);
  }

  /**
   * Return true on success. The 'in_place' parameter indicates whether the
   * the string_view input is pointing in the buffer. When in_place is false,
   * we must nearly always update the buffer.
   * @see https://url.spec.whatwg.org/#concept-ipv4-parser
   */
  [[nodiscard]] bool parse_ipv4(std::string_view input, bool in_place);

  /**
   * Return true on success.
   * @see https://url.spec.whatwg.org/#concept-ipv6-parser
   */
  [[nodiscard]] bool parse_ipv6(std::string_view input);

  /**
   * Return true on success.
   * @see https://url.spec.whatwg.org/#concept-opaque-host-parser
   */
  [[nodiscard]] bool parse_opaque_host(std::string_view input);

  ada_really_inline void parse_path(std::string_view input);

  /**
   * A URL cannot have a username/password/port if its host is null or the empty
   * string, or its scheme is "file".
   */
  [[nodiscard]] constexpr bool cannot_have_credentials_or_port() const;

  template <bool override_hostname = false>
  bool set_host_or_hostname(std::string_view input);

  ada_really_inline bool parse_host(std::string_view input);

  inline void update_base_authority(std::string_view base_buffer,
                                    const url_components& base);
  inline void update_unencoded_base_hash(std::string_view input);
  inline void update_base_hostname(std::string_view input);
  inline void update_base_search(std::string_view input);
  inline void update_base_search(std::string_view input,
                                 const uint8_t* query_percent_encode_set);
  inline void update_base_pathname(std::string_view input);
  inline void update_base_username(std::string_view input);
  inline void append_base_username(std::string_view input);
  inline void update_base_password(std::string_view input);
  inline void append_base_password(std::string_view input);
  inline void update_base_port(uint32_t input);
  inline void append_base_pathname(std::string_view input);
  [[nodiscard]] inline uint32_t retrieve_base_port() const;
  constexpr void clear_hostname();
  constexpr void clear_password();
  constexpr void clear_pathname() override;
  [[nodiscard]] constexpr bool has_dash_dot() const noexcept;
  void delete_dash_dot();
  inline void consume_prepared_path(std::string_view input);
  template <bool has_state_override = false>
  [[nodiscard]] ada_really_inline bool parse_scheme_with_colon(
      std::string_view input);
  ada_really_inline uint32_t replace_and_resize(uint32_t start, uint32_t end,
                                                std::string_view input);
  [[nodiscard]] constexpr bool has_authority() const noexcept;
  constexpr void set_protocol_as_file();
  inline void set_scheme(std::string_view new_scheme);
  /**
   * Fast function to set the scheme from a view with a colon in the
   * buffer, does not change type.
   */
  inline void set_scheme_from_view_with_colon(
      std::string_view new_scheme_with_colon);
  inline void copy_scheme(const url_aggregator& u);

  inline void update_host_to_base_host(const std::string_view input);

};  // url_aggregator

inline std::ostream& operator<<(std::ostream& out, const url& u);
}  // namespace ada

#endif
/* end file include/ada/url_aggregator.h */
/* begin file include/ada/url_aggregator-inl.h */
/**
 * @file url_aggregator-inl.h
 * @brief Inline functions for url aggregator
 */
#ifndef ADA_URL_AGGREGATOR_INL_H
#define ADA_URL_AGGREGATOR_INL_H

/* begin file include/ada/unicode-inl.h */
/**
 * @file unicode-inl.h
 * @brief Definitions for unicode operations.
 */
#ifndef ADA_UNICODE_INL_H
#define ADA_UNICODE_INL_H

/**
 * Unicode operations. These functions are not part of our public API and may
 * change at any time.
 *
 * private
 * @namespace ada::unicode
 * @brief Includes the declarations for unicode operations
 */
namespace ada::unicode {
ada_really_inline size_t percent_encode_index(const std::string_view input,
                                              const uint8_t character_set[]) {
  const char* data = input.data();
  const size_t size = input.size();

  // Process 8 bytes at a time using unrolled loop
  size_t i = 0;
  for (; i + 8 <= size; i += 8) {
    unsigned char chunk[8];
    std::memcpy(&chunk, data + i,
                8);  // entices compiler to unconditionally process 8 characters

    // Check 8 characters at once
    for (size_t j = 0; j < 8; j++) {
      if (character_sets::bit_at(character_set, chunk[j])) {
        return i + j;
      }
    }
  }

  // Handle remaining bytes
  for (; i < size; i++) {
    if (character_sets::bit_at(character_set, data[i])) {
      return i;
    }
  }

  return size;
}
}  // namespace ada::unicode

#endif  // ADA_UNICODE_INL_H
/* end file include/ada/unicode-inl.h */

#include <charconv>
#include <ostream>
#include <string_view>

namespace ada {

inline void url_aggregator::update_base_authority(
    std::string_view base_buffer, const ada::url_components& base) {
  std::string_view input = base_buffer.substr(
      base.protocol_end, base.host_start - base.protocol_end);
  ada_log("url_aggregator::update_base_authority ", input);

  bool input_starts_with_dash = input.starts_with("//");
  uint32_t diff = components.host_start - components.protocol_end;

  buffer.erase(components.protocol_end,
               components.host_start - components.protocol_end);
  components.username_end = components.protocol_end;

  if (input_starts_with_dash) {
    input.remove_prefix(2);
    diff += 2;  // add "//"
    buffer.insert(components.protocol_end, "//");
    components.username_end += 2;
  }

  size_t password_delimiter = input.find(':');

  // Check if input contains both username and password by checking the
  // delimiter: ":" A typical input that contains authority would be "user:pass"
  if (password_delimiter != std::string_view::npos) {
    // Insert both username and password
    std::string_view username = input.substr(0, password_delimiter);
    std::string_view password = input.substr(password_delimiter + 1);

    buffer.insert(components.protocol_end + diff, username);
    diff += uint32_t(username.size());
    buffer.insert(components.protocol_end + diff, ":");
    components.username_end = components.protocol_end + diff;
    buffer.insert(components.protocol_end + diff + 1, password);
    diff += uint32_t(password.size()) + 1;
  } else if (!input.empty()) {
    // Insert only username
    buffer.insert(components.protocol_end + diff, input);
    components.username_end =
        components.protocol_end + diff + uint32_t(input.size());
    diff += uint32_t(input.size());
  }

  components.host_start += diff;

  if (buffer.size() > base.host_start && buffer[base.host_start] != '@') {
    buffer.insert(components.host_start, "@");
    diff++;
  }
  components.host_end += diff;
  components.pathname_start += diff;
  if (components.search_start != url_components::omitted) {
    components.search_start += diff;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += diff;
  }
}

inline void url_aggregator::update_unencoded_base_hash(std::string_view input) {
  ada_log("url_aggregator::update_unencoded_base_hash ", input, " [",
          input.size(), " bytes], buffer is '", buffer, "' [", buffer.size(),
          " bytes] components.hash_start = ", components.hash_start);
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
  if (components.hash_start != url_components::omitted) {
    buffer.resize(components.hash_start);
  }
  components.hash_start = uint32_t(buffer.size());
  buffer += "#";
  bool encoding_required = unicode::percent_encode<true>(
      input, ada::character_sets::FRAGMENT_PERCENT_ENCODE, buffer);
  // When encoding_required is false, then buffer is left unchanged, and percent
  // encoding was not deemed required.
  if (!encoding_required) {
    buffer.append(input);
  }
  ada_log("url_aggregator::update_unencoded_base_hash final buffer is '",
          buffer, "' [", buffer.size(), " bytes]");
  ADA_ASSERT_TRUE(validate());
}

ada_really_inline uint32_t url_aggregator::replace_and_resize(
    uint32_t start, uint32_t end, std::string_view input) {
  uint32_t current_length = end - start;
  uint32_t input_size = uint32_t(input.size());
  uint32_t new_difference = input_size - current_length;

  if (current_length == 0) {
    buffer.insert(start, input);
  } else if (input_size == current_length) {
    buffer.replace(start, input_size, input);
  } else if (input_size < current_length) {
    buffer.erase(start, current_length - input_size);
    buffer.replace(start, input_size, input);
  } else {
    buffer.replace(start, current_length, input.substr(0, current_length));
    buffer.insert(start + current_length, input.substr(current_length));
  }

  return new_difference;
}

inline void url_aggregator::update_base_hostname(const std::string_view input) {
  ada_log("url_aggregator::update_base_hostname ", input, " [", input.size(),
          " bytes], buffer is '", buffer, "' [", buffer.size(), " bytes]");
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));

  // This next line is required for when parsing a URL like `foo://`
  add_authority_slashes_if_needed();

  bool has_credentials = components.protocol_end + 2 < components.host_start;
  uint32_t new_difference =
      replace_and_resize(components.host_start, components.host_end, input);

  if (has_credentials) {
    buffer.insert(components.host_start, "@");
    new_difference++;
  }
  components.host_end += new_difference;
  components.pathname_start += new_difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += new_difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += new_difference;
  }
  ADA_ASSERT_TRUE(validate());
}

[[nodiscard]] ada_really_inline uint32_t
url_aggregator::get_pathname_length() const noexcept {
  ada_log("url_aggregator::get_pathname_length");
  uint32_t ending_index = uint32_t(buffer.size());
  if (components.search_start != url_components::omitted) {
    ending_index = components.search_start;
  } else if (components.hash_start != url_components::omitted) {
    ending_index = components.hash_start;
  }
  return ending_index - components.pathname_start;
}

[[nodiscard]] ada_really_inline bool url_aggregator::is_at_path()
    const noexcept {
  return buffer.size() == components.pathname_start;
}

inline void url_aggregator::update_base_search(std::string_view input) {
  ada_log("url_aggregator::update_base_search ", input);
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
  if (input.empty()) {
    clear_search();
    return;
  }

  if (input[0] == '?') {
    input.remove_prefix(1);
  }

  if (components.hash_start == url_components::omitted) {
    if (components.search_start == url_components::omitted) {
      components.search_start = uint32_t(buffer.size());
      buffer += "?";
    } else {
      buffer.resize(components.search_start + 1);
    }

    buffer.append(input);
  } else {
    if (components.search_start == url_components::omitted) {
      components.search_start = components.hash_start;
    } else {
      buffer.erase(components.search_start,
                   components.hash_start - components.search_start);
      components.hash_start = components.search_start;
    }

    buffer.insert(components.search_start, "?");
    buffer.insert(components.search_start + 1, input);
    components.hash_start += uint32_t(input.size() + 1);  // Do not forget `?`
  }

  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::update_base_search(
    std::string_view input, const uint8_t query_percent_encode_set[]) {
  ada_log("url_aggregator::update_base_search ", input,
          " with encoding parameter ", to_string(), "\n", to_diagram());
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));

  if (components.hash_start == url_components::omitted) {
    if (components.search_start == url_components::omitted) {
      components.search_start = uint32_t(buffer.size());
      buffer += "?";
    } else {
      buffer.resize(components.search_start + 1);
    }

    bool encoding_required =
        unicode::percent_encode<true>(input, query_percent_encode_set, buffer);
    // When encoding_required is false, then buffer is left unchanged, and
    // percent encoding was not deemed required.
    if (!encoding_required) {
      buffer.append(input);
    }
  } else {
    if (components.search_start == url_components::omitted) {
      components.search_start = components.hash_start;
    } else {
      buffer.erase(components.search_start,
                   components.hash_start - components.search_start);
      components.hash_start = components.search_start;
    }

    buffer.insert(components.search_start, "?");
    size_t idx =
        ada::unicode::percent_encode_index(input, query_percent_encode_set);
    if (idx == input.size()) {
      buffer.insert(components.search_start + 1, input);
      components.hash_start += uint32_t(input.size() + 1);  // Do not forget `?`
    } else {
      buffer.insert(components.search_start + 1, input, 0, idx);
      input.remove_prefix(idx);
      // We only create a temporary string if we need percent encoding and
      // we attempt to create as small a temporary string as we can.
      std::string encoded =
          ada::unicode::percent_encode(input, query_percent_encode_set);
      buffer.insert(components.search_start + idx + 1, encoded);
      components.hash_start +=
          uint32_t(encoded.size() + idx + 1);  // Do not forget `?`
    }
  }

  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::update_base_pathname(const std::string_view input) {
  ada_log("url_aggregator::update_base_pathname '", input, "' [", input.size(),
          " bytes] \n", to_diagram());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
  ADA_ASSERT_TRUE(validate());

  const bool begins_with_dashdash = input.starts_with("//");
  if (!begins_with_dashdash && has_dash_dot()) {
    // We must delete the ./
    delete_dash_dot();
  }

  if (begins_with_dashdash && !has_opaque_path && !has_authority() &&
      !has_dash_dot()) {
    // If url's host is null, url does not have an opaque path, url's path's
    // size is greater than 1, then append U+002F (/) followed by U+002E (.) to
    // output.
    buffer.insert(components.pathname_start, "/.");
    components.pathname_start += 2;
    if (components.search_start != url_components::omitted) {
      components.search_start += 2;
    }
    if (components.hash_start != url_components::omitted) {
      components.hash_start += 2;
    }
  }

  uint32_t difference = replace_and_resize(
      components.pathname_start,
      components.pathname_start + get_pathname_length(), input);
  if (components.search_start != url_components::omitted) {
    components.search_start += difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += difference;
  }
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::append_base_pathname(const std::string_view input) {
  ada_log("url_aggregator::append_base_pathname ", input, " ", to_string(),
          "\n", to_diagram());
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
#if ADA_DEVELOPMENT_CHECKS
  // computing the expected password.
  std::string path_expected(get_pathname());
  path_expected.append(input);
#endif  // ADA_DEVELOPMENT_CHECKS
  uint32_t ending_index = uint32_t(buffer.size());
  if (components.search_start != url_components::omitted) {
    ending_index = components.search_start;
  } else if (components.hash_start != url_components::omitted) {
    ending_index = components.hash_start;
  }
  buffer.insert(ending_index, input);

  if (components.search_start != url_components::omitted) {
    components.search_start += uint32_t(input.size());
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += uint32_t(input.size());
  }
#if ADA_DEVELOPMENT_CHECKS
  std::string path_after = std::string(get_pathname());
  ADA_ASSERT_EQUAL(
      path_expected, path_after,
      "append_base_pathname problem after inserting " + std::string(input));
#endif  // ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::update_base_username(const std::string_view input) {
  ada_log("url_aggregator::update_base_username '", input, "' ", to_string(),
          "\n", to_diagram());
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));

  add_authority_slashes_if_needed();

  bool has_password = has_non_empty_password();
  bool host_starts_with_at = buffer.size() > components.host_start &&
                             buffer[components.host_start] == '@';
  uint32_t diff = replace_and_resize(components.protocol_end + 2,
                                     components.username_end, input);

  components.username_end += diff;
  components.host_start += diff;

  if (!input.empty() && !host_starts_with_at) {
    buffer.insert(components.host_start, "@");
    diff++;
  } else if (input.empty() && host_starts_with_at && !has_password) {
    // Input is empty, there is no password, and we need to remove "@" from
    // hostname
    buffer.erase(components.host_start, 1);
    diff--;
  }

  components.host_end += diff;
  components.pathname_start += diff;
  if (components.search_start != url_components::omitted) {
    components.search_start += diff;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += diff;
  }
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::append_base_username(const std::string_view input) {
  ada_log("url_aggregator::append_base_username ", input);
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
#if ADA_DEVELOPMENT_CHECKS
  // computing the expected password.
  std::string username_expected(get_username());
  username_expected.append(input);
#endif  // ADA_DEVELOPMENT_CHECKS
  add_authority_slashes_if_needed();

  // If input is empty, do nothing.
  if (input.empty()) {
    return;
  }

  uint32_t difference = uint32_t(input.size());
  buffer.insert(components.username_end, input);
  components.username_end += difference;
  components.host_start += difference;

  if (buffer[components.host_start] != '@' &&
      components.host_start != components.host_end) {
    buffer.insert(components.host_start, "@");
    difference++;
  }

  components.host_end += difference;
  components.pathname_start += difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += difference;
  }
#if ADA_DEVELOPMENT_CHECKS
  std::string username_after(get_username());
  ADA_ASSERT_EQUAL(
      username_expected, username_after,
      "append_base_username problem after inserting " + std::string(input));
#endif  // ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_TRUE(validate());
}

constexpr void url_aggregator::clear_password() {
  ada_log("url_aggregator::clear_password ", to_string());
  ADA_ASSERT_TRUE(validate());
  if (!has_password()) {
    return;
  }

  uint32_t diff = components.host_start - components.username_end;
  buffer.erase(components.username_end, diff);
  components.host_start -= diff;
  components.host_end -= diff;
  components.pathname_start -= diff;
  if (components.search_start != url_components::omitted) {
    components.search_start -= diff;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start -= diff;
  }
}

inline void url_aggregator::update_base_password(const std::string_view input) {
  ada_log("url_aggregator::update_base_password ", input);
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));

  add_authority_slashes_if_needed();

  // TODO: Optimization opportunity. Merge the following removal functions.
  if (input.empty()) {
    clear_password();

    // Remove username too, if it is empty.
    if (!has_non_empty_username()) {
      update_base_username("");
    }

    return;
  }

  bool password_exists = has_password();
  uint32_t difference = uint32_t(input.size());

  if (password_exists) {
    uint32_t current_length =
        components.host_start - components.username_end - 1;
    buffer.erase(components.username_end + 1, current_length);
    difference -= current_length;
  } else {
    buffer.insert(components.username_end, ":");
    difference++;
  }

  buffer.insert(components.username_end + 1, input);
  components.host_start += difference;

  // The following line is required to add "@" to hostname. When updating
  // password if hostname does not start with "@", it is "update_base_password"s
  // responsibility to set it.
  if (buffer[components.host_start] != '@') {
    buffer.insert(components.host_start, "@");
    difference++;
  }

  components.host_end += difference;
  components.pathname_start += difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += difference;
  }
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::append_base_password(const std::string_view input) {
  ada_log("url_aggregator::append_base_password ", input, " ", to_string(),
          "\n", to_diagram());
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
#if ADA_DEVELOPMENT_CHECKS
  // computing the expected password.
  std::string password_expected = std::string(get_password());
  password_expected.append(input);
#endif  // ADA_DEVELOPMENT_CHECKS
  add_authority_slashes_if_needed();

  // If input is empty, do nothing.
  if (input.empty()) {
    return;
  }

  uint32_t difference = uint32_t(input.size());
  if (has_password()) {
    buffer.insert(components.host_start, input);
  } else {
    difference++;  // Increment for ":"
    buffer.insert(components.username_end, ":");
    buffer.insert(components.username_end + 1, input);
  }
  components.host_start += difference;

  // The following line is required to add "@" to hostname. When updating
  // password if hostname does not start with "@", it is "append_base_password"s
  // responsibility to set it.
  if (buffer[components.host_start] != '@') {
    buffer.insert(components.host_start, "@");
    difference++;
  }

  components.host_end += difference;
  components.pathname_start += difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += difference;
  }
#if ADA_DEVELOPMENT_CHECKS
  std::string password_after(get_password());
  ADA_ASSERT_EQUAL(
      password_expected, password_after,
      "append_base_password problem after inserting " + std::string(input));
#endif  // ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::update_base_port(uint32_t input) {
  ada_log("url_aggregator::update_base_port");
  ADA_ASSERT_TRUE(validate());
  if (input == url_components::omitted) {
    clear_port();
    return;
  }
  // calling std::to_string(input.value()) is unfortunate given that the port
  // value is probably already available as a string.
  std::string value = helpers::concat(":", std::to_string(input));
  uint32_t difference = uint32_t(value.size());

  if (components.port != url_components::omitted) {
    difference -= components.pathname_start - components.host_end;
    buffer.erase(components.host_end,
                 components.pathname_start - components.host_end);
  }

  buffer.insert(components.host_end, value);
  components.pathname_start += difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += difference;
  }
  components.port = input;
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::clear_port() {
  ada_log("url_aggregator::clear_port");
  ADA_ASSERT_TRUE(validate());
  if (components.port == url_components::omitted) {
    return;
  }
  uint32_t length = components.pathname_start - components.host_end;
  buffer.erase(components.host_end, length);
  components.pathname_start -= length;
  if (components.search_start != url_components::omitted) {
    components.search_start -= length;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start -= length;
  }
  components.port = url_components::omitted;
  ADA_ASSERT_TRUE(validate());
}

[[nodiscard]] inline uint32_t url_aggregator::retrieve_base_port() const {
  ada_log("url_aggregator::retrieve_base_port");
  return components.port;
}

inline void url_aggregator::clear_search() {
  ada_log("url_aggregator::clear_search");
  ADA_ASSERT_TRUE(validate());
  if (components.search_start == url_components::omitted) {
    return;
  }

  if (components.hash_start == url_components::omitted) {
    buffer.resize(components.search_start);
  } else {
    buffer.erase(components.search_start,
                 components.hash_start - components.search_start);
    components.hash_start = components.search_start;
  }

  components.search_start = url_components::omitted;

#if ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_EQUAL(get_search(), "",
                   "search should have been cleared on buffer=" + buffer +
                       " with " + components.to_string() + "\n" + to_diagram());
#endif
  ADA_ASSERT_TRUE(validate());
}

inline void url_aggregator::clear_hash() {
  ada_log("url_aggregator::clear_hash");
  ADA_ASSERT_TRUE(validate());
  if (components.hash_start == url_components::omitted) {
    return;
  }
  buffer.resize(components.hash_start);
  components.hash_start = url_components::omitted;

#if ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_EQUAL(get_hash(), "",
                   "hash should have been cleared on buffer=" + buffer +
                       " with " + components.to_string() + "\n" + to_diagram());
#endif
  ADA_ASSERT_TRUE(validate());
}

constexpr void url_aggregator::clear_pathname() {
  ada_log("url_aggregator::clear_pathname");
  ADA_ASSERT_TRUE(validate());
  uint32_t ending_index = uint32_t(buffer.size());
  if (components.search_start != url_components::omitted) {
    ending_index = components.search_start;
  } else if (components.hash_start != url_components::omitted) {
    ending_index = components.hash_start;
  }
  uint32_t pathname_length = ending_index - components.pathname_start;
  buffer.erase(components.pathname_start, pathname_length);
  uint32_t difference = pathname_length;
  if (components.pathname_start == components.host_end + 2 &&
      buffer[components.host_end] == '/' &&
      buffer[components.host_end + 1] == '.') {
    components.pathname_start -= 2;
    buffer.erase(components.host_end, 2);
    difference += 2;
  }
  if (components.search_start != url_components::omitted) {
    components.search_start -= difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start -= difference;
  }
  ada_log("url_aggregator::clear_pathname completed, running checks...");
#if ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_EQUAL(get_pathname(), "",
                   "pathname should have been cleared on buffer=" + buffer +
                       " with " + components.to_string() + "\n" + to_diagram());
#endif
  ADA_ASSERT_TRUE(validate());
  ada_log("url_aggregator::clear_pathname completed, running checks... ok");
}

constexpr void url_aggregator::clear_hostname() {
  ada_log("url_aggregator::clear_hostname");
  ADA_ASSERT_TRUE(validate());
  if (!has_authority()) {
    return;
  }
  ADA_ASSERT_TRUE(has_authority());

  uint32_t hostname_length = components.host_end - components.host_start;
  uint32_t start = components.host_start;

  // If hostname starts with "@", we should not remove that character.
  if (hostname_length > 0 && buffer[start] == '@') {
    start++;
    hostname_length--;
  }
  buffer.erase(start, hostname_length);
  components.host_end = start;
  components.pathname_start -= hostname_length;
  if (components.search_start != url_components::omitted) {
    components.search_start -= hostname_length;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start -= hostname_length;
  }
#if ADA_DEVELOPMENT_CHECKS
  ADA_ASSERT_EQUAL(get_hostname(), "",
                   "hostname should have been cleared on buffer=" + buffer +
                       " with " + components.to_string() + "\n" + to_diagram());
#endif
  ADA_ASSERT_TRUE(has_authority());
  ADA_ASSERT_EQUAL(has_empty_hostname(), true,
                   "hostname should have been cleared on buffer=" + buffer +
                       " with " + components.to_string() + "\n" + to_diagram());
  ADA_ASSERT_TRUE(validate());
}

[[nodiscard]] constexpr bool url_aggregator::has_hash() const noexcept {
  ada_log("url_aggregator::has_hash");
  return components.hash_start != url_components::omitted;
}

[[nodiscard]] constexpr bool url_aggregator::has_search() const noexcept {
  ada_log("url_aggregator::has_search");
  return components.search_start != url_components::omitted;
}

constexpr bool url_aggregator::has_credentials() const noexcept {
  ada_log("url_aggregator::has_credentials");
  return has_non_empty_username() || has_non_empty_password();
}

constexpr bool url_aggregator::cannot_have_credentials_or_port() const {
  ada_log("url_aggregator::cannot_have_credentials_or_port");
  return type == ada::scheme::type::FILE ||
         components.host_start == components.host_end;
}

[[nodiscard]] ada_really_inline const ada::url_components&
url_aggregator::get_components() const noexcept {
  return components;
}

[[nodiscard]] constexpr bool ada::url_aggregator::has_authority()
    const noexcept {
  ada_log("url_aggregator::has_authority");
  // Performance: instead of doing this potentially expensive check, we could
  // have a boolean in the struct.
  return components.protocol_end + 2 <= components.host_start &&
         buffer[components.protocol_end] == '/' &&
         buffer[components.protocol_end + 1] == '/';
}

inline void ada::url_aggregator::add_authority_slashes_if_needed() {
  ada_log("url_aggregator::add_authority_slashes_if_needed");
  ADA_ASSERT_TRUE(validate());
  // Protocol setter will insert `http:` to the URL. It is up to hostname setter
  // to insert
  // `//` initially to the buffer, since it depends on the hostname existence.
  if (has_authority()) {
    return;
  }
  // Performance: the common case is components.protocol_end == buffer.size()
  // Optimization opportunity: in many cases, the "//" is part of the input and
  // the insert could be fused with another insert.
  buffer.insert(components.protocol_end, "//");
  components.username_end += 2;
  components.host_start += 2;
  components.host_end += 2;
  components.pathname_start += 2;
  if (components.search_start != url_components::omitted) {
    components.search_start += 2;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += 2;
  }
  ADA_ASSERT_TRUE(validate());
}

constexpr void ada::url_aggregator::reserve(uint32_t capacity) {
  buffer.reserve(capacity);
}

constexpr bool url_aggregator::has_non_empty_username() const noexcept {
  ada_log("url_aggregator::has_non_empty_username");
  return components.protocol_end + 2 < components.username_end;
}

constexpr bool url_aggregator::has_non_empty_password() const noexcept {
  ada_log("url_aggregator::has_non_empty_password");
  return components.host_start > components.username_end;
}

constexpr bool url_aggregator::has_password() const noexcept {
  ada_log("url_aggregator::has_password");
  // This function does not care about the length of the password
  return components.host_start > components.username_end &&
         buffer[components.username_end] == ':';
}

constexpr bool url_aggregator::has_empty_hostname() const noexcept {
  if (!has_hostname()) {
    return false;
  }
  if (components.host_start == components.host_end) {
    return true;
  }
  if (components.host_end > components.host_start + 1) {
    return false;
  }
  return components.username_end != components.host_start;
}

constexpr bool url_aggregator::has_hostname() const noexcept {
  return has_authority();
}

constexpr bool url_aggregator::has_port() const noexcept {
  ada_log("url_aggregator::has_port");
  // A URL cannot have a username/password/port if its host is null or the empty
  // string, or its scheme is "file".
  return has_hostname() && components.pathname_start != components.host_end;
}

[[nodiscard]] constexpr bool url_aggregator::has_dash_dot() const noexcept {
  // If url's host is null, url does not have an opaque path, url's path's size
  // is greater than 1, and url's path[0] is the empty string, then append
  // U+002F (/) followed by U+002E (.) to output.
  ada_log("url_aggregator::has_dash_dot");
#if ADA_DEVELOPMENT_CHECKS
  // If pathname_start and host_end are exactly two characters apart, then we
  // either have a one-digit port such as http://test.com:5?param=1 or else we
  // have a /.: sequence such as "non-spec:/.//". We test that this is the case.
  if (components.pathname_start == components.host_end + 2) {
    ADA_ASSERT_TRUE((buffer[components.host_end] == '/' &&
                     buffer[components.host_end + 1] == '.') ||
                    (buffer[components.host_end] == ':' &&
                     checkers::is_digit(buffer[components.host_end + 1])));
  }
  if (components.pathname_start == components.host_end + 2 &&
      buffer[components.host_end] == '/' &&
      buffer[components.host_end + 1] == '.') {
    ADA_ASSERT_TRUE(components.pathname_start + 1 < buffer.size());
    ADA_ASSERT_TRUE(buffer[components.pathname_start] == '/');
    ADA_ASSERT_TRUE(buffer[components.pathname_start + 1] == '/');
  }
#endif
  // Performance: it should be uncommon for components.pathname_start ==
  // components.host_end + 2 to be true. So we put this check first in the
  // sequence. Most times, we do not have an opaque path. Checking for '/.' is
  // more expensive, but should be uncommon.
  return components.pathname_start == components.host_end + 2 &&
         !has_opaque_path && buffer[components.host_end] == '/' &&
         buffer[components.host_end + 1] == '.';
}

[[nodiscard]] constexpr std::string_view url_aggregator::get_href()
    const noexcept ada_lifetime_bound {
  ada_log("url_aggregator::get_href");
  return buffer;
}

[[nodiscard]] constexpr size_t url_aggregator::get_href_size() const noexcept {
  return buffer.size();
}

ada_really_inline size_t
url_aggregator::parse_port(std::string_view view, bool check_trailing_content) {
  ada_log("url_aggregator::parse_port('", view, "') ", view.size());
  if (!view.empty() && view[0] == '-') {
    ada_log("parse_port: view[0] == '0' && view.size() > 1");
    is_valid = false;
    return 0;
  }
  uint16_t parsed_port{};
  auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port);
  if (r.ec == std::errc::result_out_of_range) {
    ada_log("parse_port: r.ec == std::errc::result_out_of_range");
    is_valid = false;
    return 0;
  }
  ada_log("parse_port: ", parsed_port);
  const size_t consumed = size_t(r.ptr - view.data());
  ada_log("parse_port: consumed ", consumed);
  if (check_trailing_content) {
    is_valid &=
        (consumed == view.size() || view[consumed] == '/' ||
         view[consumed] == '?' || (is_special() && view[consumed] == '\\'));
  }
  ada_log("parse_port: is_valid = ", is_valid);
  if (is_valid) {
    ada_log("parse_port", r.ec == std::errc());
    // scheme_default_port can return 0, and we should allow 0 as a base port.
    auto default_port = scheme_default_port();
    bool is_port_valid = (default_port == 0 && parsed_port == 0) ||
                         (default_port != parsed_port);
    if (r.ec == std::errc() && is_port_valid) {
      update_base_port(parsed_port);
    } else {
      clear_port();
    }
  }
  return consumed;
}

constexpr void url_aggregator::set_protocol_as_file() {
  ada_log("url_aggregator::set_protocol_as_file ");
  ADA_ASSERT_TRUE(validate());
  type = ada::scheme::type::FILE;
  // next line could overflow but unsigned arithmetic has well-defined
  // overflows.
  uint32_t new_difference = 5 - components.protocol_end;

  if (buffer.empty()) {
    buffer.append("file:");
  } else {
    buffer.erase(0, components.protocol_end);
    buffer.insert(0, "file:");
  }
  components.protocol_end = 5;

  // Update the rest of the components.
  components.username_end += new_difference;
  components.host_start += new_difference;
  components.host_end += new_difference;
  components.pathname_start += new_difference;
  if (components.search_start != url_components::omitted) {
    components.search_start += new_difference;
  }
  if (components.hash_start != url_components::omitted) {
    components.hash_start += new_difference;
  }
  ADA_ASSERT_TRUE(validate());
}

[[nodiscard]] constexpr bool url_aggregator::validate() const noexcept {
  if (!is_valid) {
    return true;
  }
  if (!components.check_offset_consistency()) {
    ada_log("url_aggregator::validate inconsistent components \n",
            to_diagram());
    return false;
  }
  // We have a credible components struct, but let us investivate more
  // carefully:
  /**
   * https://user:pass@example.com:1234/foo/bar?baz#quux
   *       |     |    |          | ^^^^|       |   |
   *       |     |    |          | |   |       |   `----- hash_start
   *       |     |    |          | |   |       `--------- search_start
   *       |     |    |          | |   `----------------- pathname_start
   *       |     |    |          | `--------------------- port
   *       |     |    |          `----------------------- host_end
   *       |     |    `---------------------------------- host_start
   *       |     `--------------------------------------- username_end
   *       `--------------------------------------------- protocol_end
   */
  if (components.protocol_end == url_components::omitted) {
    ada_log("url_aggregator::validate omitted protocol_end \n", to_diagram());
    return false;
  }
  if (components.username_end == url_components::omitted) {
    ada_log("url_aggregator::validate omitted username_end \n", to_diagram());
    return false;
  }
  if (components.host_start == url_components::omitted) {
    ada_log("url_aggregator::validate omitted host_start \n", to_diagram());
    return false;
  }
  if (components.host_end == url_components::omitted) {
    ada_log("url_aggregator::validate omitted host_end \n", to_diagram());
    return false;
  }
  if (components.pathname_start == url_components::omitted) {
    ada_log("url_aggregator::validate omitted pathname_start \n", to_diagram());
    return false;
  }

  if (components.protocol_end > buffer.size()) {
    ada_log("url_aggregator::validate protocol_end overflow \n", to_diagram());
    return false;
  }
  if (components.username_end > buffer.size()) {
    ada_log("url_aggregator::validate username_end overflow \n", to_diagram());
    return false;
  }
  if (components.host_start > buffer.size()) {
    ada_log("url_aggregator::validate host_start overflow \n", to_diagram());
    return false;
  }
  if (components.host_end > buffer.size()) {
    ada_log("url_aggregator::validate host_end overflow \n", to_diagram());
    return false;
  }
  if (components.pathname_start > buffer.size()) {
    ada_log("url_aggregator::validate pathname_start overflow \n",
            to_diagram());
    return false;
  }

  if (components.protocol_end > 0) {
    if (buffer[components.protocol_end - 1] != ':') {
      ada_log(
          "url_aggregator::validate missing : at the end of the protocol \n",
          to_diagram());
      return false;
    }
  }

  if (components.username_end != buffer.size() &&
      components.username_end > components.protocol_end + 2) {
    if (buffer[components.username_end] != ':' &&
        buffer[components.username_end] != '@') {
      ada_log(
          "url_aggregator::validate missing : or @ at the end of the username "
          "\n",
          to_diagram());
      return false;
    }
  }

  if (components.host_start != buffer.size()) {
    if (components.host_start > components.username_end) {
      if (buffer[components.host_start] != '@') {
        ada_log(
            "url_aggregator::validate missing @ at the end of the password \n",
            to_diagram());
        return false;
      }
    } else if (components.host_start == components.username_end &&
               components.host_end > components.host_start) {
      if (components.host_start == components.protocol_end + 2) {
        if (buffer[components.protocol_end] != '/' ||
            buffer[components.protocol_end + 1] != '/') {
          ada_log(
              "url_aggregator::validate missing // between protocol and host "
              "\n",
              to_diagram());
          return false;
        }
      } else {
        if (components.host_start > components.protocol_end &&
            buffer[components.host_start] != '@') {
          ada_log(
              "url_aggregator::validate missing @ at the end of the username "
              "\n",
              to_diagram());
          return false;
        }
      }
    } else {
      if (components.host_end != components.host_start) {
        ada_log("url_aggregator::validate expected omitted host \n",
                to_diagram());
        return false;
      }
    }
  }
  if (components.host_end != buffer.size() &&
      components.pathname_start > components.host_end) {
    if (components.pathname_start == components.host_end + 2 &&
        buffer[components.host_end] == '/' &&
        buffer[components.host_end + 1] == '.') {
      if (components.pathname_start + 1 >= buffer.size() ||
          buffer[components.pathname_start] != '/' ||
          buffer[components.pathname_start + 1] != '/') {
        ada_log(
            "url_aggregator::validate expected the path to begin with // \n",
            to_diagram());
        return false;
      }
    } else if (buffer[components.host_end] != ':') {
      ada_log("url_aggregator::validate missing : at the port \n",
              to_diagram());
      return false;
    }
  }
  if (components.pathname_start != buffer.size() &&
      components.pathname_start < components.search_start &&
      components.pathname_start < components.hash_start && !has_opaque_path) {
    if (buffer[components.pathname_start] != '/') {
      ada_log("url_aggregator::validate missing / at the path \n",
              to_diagram());
      return false;
    }
  }
  if (components.search_start != url_components::omitted) {
    if (buffer[components.search_start] != '?') {
      ada_log("url_aggregator::validate missing ? at the search \n",
              to_diagram());
      return false;
    }
  }
  if (components.hash_start != url_components::omitted) {
    if (buffer[components.hash_start] != '#') {
      ada_log("url_aggregator::validate missing # at the hash \n",
              to_diagram());
      return false;
    }
  }

  return true;
}

[[nodiscard]] constexpr std::string_view url_aggregator::get_pathname() const
    ada_lifetime_bound {
  ada_log("url_aggregator::get_pathname pathname_start = ",
          components.pathname_start, " buffer.size() = ", buffer.size(),
          " components.search_start = ", components.search_start,
          " components.hash_start = ", components.hash_start);
  auto ending_index = uint32_t(buffer.size());
  if (components.search_start != url_components::omitted) {
    ending_index = components.search_start;
  } else if (components.hash_start != url_components::omitted) {
    ending_index = components.hash_start;
  }
  return helpers::substring(buffer, components.pathname_start, ending_index);
}

inline std::ostream& operator<<(std::ostream& out,
                                const ada::url_aggregator& u) {
  return out << u.to_string();
}

void url_aggregator::update_host_to_base_host(const std::string_view input) {
  ada_log("url_aggregator::update_host_to_base_host ", input);
  ADA_ASSERT_TRUE(validate());
  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
  if (type != ada::scheme::type::FILE) {
    // Let host be the result of host parsing host_view with url is not special.
    if (input.empty() && !is_special()) {
      if (has_hostname()) {
        clear_hostname();
      } else if (has_dash_dot()) {
        add_authority_slashes_if_needed();
        delete_dash_dot();
      }
      return;
    }
  }
  update_base_hostname(input);
  ADA_ASSERT_TRUE(validate());
  return;
}
}  // namespace ada

#endif  // ADA_URL_AGGREGATOR_INL_H
/* end file include/ada/url_aggregator-inl.h */
/* begin file include/ada/url_search_params.h */
/**
 * @file url_search_params.h
 * @brief URL query string parameter manipulation.
 *
 * This file provides the `url_search_params` class for parsing, manipulating,
 * and serializing URL query strings. It implements the URLSearchParams API
 * from the WHATWG URL Standard.
 *
 * @see https://url.spec.whatwg.org/#interface-urlsearchparams
 */
#ifndef ADA_URL_SEARCH_PARAMS_H
#define ADA_URL_SEARCH_PARAMS_H

#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace ada {

/**
 * @brief Iterator types for url_search_params iteration.
 */
enum class url_search_params_iter_type {
  KEYS,    /**< Iterate over parameter keys only */
  VALUES,  /**< Iterate over parameter values only */
  ENTRIES, /**< Iterate over key-value pairs */
};

template <typename T, url_search_params_iter_type Type>
struct url_search_params_iter;

/** Type alias for a key-value pair of string views. */
typedef std::pair<std::string_view, std::string_view> key_value_view_pair;

/** Iterator over search parameter keys. */
using url_search_params_keys_iter =
    url_search_params_iter<std::string_view, url_search_params_iter_type::KEYS>;
/** Iterator over search parameter values. */
using url_search_params_values_iter =
    url_search_params_iter<std::string_view,
                           url_search_params_iter_type::VALUES>;
/** Iterator over search parameter key-value pairs. */
using url_search_params_entries_iter =
    url_search_params_iter<key_value_view_pair,
                           url_search_params_iter_type::ENTRIES>;

/**
 * @brief Class for parsing and manipulating URL query strings.
 *
 * The `url_search_params` class provides methods to parse, modify, and
 * serialize URL query parameters (the part after '?' in a URL). It handles
 * percent-encoding and decoding automatically.
 *
 * All string inputs must be valid UTF-8. The caller is responsible for
 * ensuring UTF-8 validity.
 *
 * Construction and `reset` refuse query strings longer than
 * `get_max_input_length()` (the object is left empty). Individual `append` /
 * `set` calls are not length-capped.
 *
 * @see https://url.spec.whatwg.org/#interface-urlsearchparams
 */
struct url_search_params {
  url_search_params() = default;

  /**
   * Constructs url_search_params by parsing a query string.
   * @param input A query string (with or without leading '?'). Must be UTF-8.
   *        If longer than `get_max_input_length()`, the object stays empty.
   */
  explicit url_search_params(const std::string_view input) {
    initialize(input);
  }

  url_search_params(const url_search_params& u) = default;
  url_search_params(url_search_params&& u) noexcept = default;
  url_search_params& operator=(url_search_params&& u) noexcept = default;
  url_search_params& operator=(const url_search_params& u) = default;
  ~url_search_params() = default;

  /**
   * Returns the number of key-value pairs.
   * @return The total count of parameters.
   */
  [[nodiscard]] inline size_t size() const noexcept;

  /**
   * Appends a new key-value pair to the parameter list.
   * @param key The parameter name (must be valid UTF-8).
   * @param value The parameter value (must be valid UTF-8).
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append
   */
  inline void append(std::string_view key, std::string_view value);

  /**
   * Removes all pairs with the given key.
   * @param key The parameter name to remove.
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-delete
   */
  inline void remove(std::string_view key);

  /**
   * Removes all pairs with the given key and value.
   * @param key The parameter name.
   * @param value The parameter value to match.
   */
  inline void remove(std::string_view key, std::string_view value);

  /**
   * Returns the value of the first pair with the given key.
   * @param key The parameter name to search for.
   * @return The value if found, or std::nullopt if not present.
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get
   */
  inline std::optional<std::string_view> get(std::string_view key);

  /**
   * Returns all values for pairs with the given key.
   * @param key The parameter name to search for.
   * @return A vector of all matching values (may be empty).
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall
   */
  inline std::vector<std::string> get_all(std::string_view key);

  /**
   * Checks if any pair has the given key.
   * @param key The parameter name to search for.
   * @return `true` if at least one pair has this key.
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
   */
  inline bool has(std::string_view key) noexcept;

  /**
   * Checks if any pair matches the given key and value.
   * @param key The parameter name to search for.
   * @param value The parameter value to match.
   * @return `true` if a matching pair exists.
   */
  inline bool has(std::string_view key, std::string_view value) noexcept;

  /**
   * Sets a parameter value, replacing any existing pairs with the same key.
   * @param key The parameter name (must be valid UTF-8).
   * @param value The parameter value (must be valid UTF-8).
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set
   */
  inline void set(std::string_view key, std::string_view value);

  /**
   * Sorts all key-value pairs by their keys using code unit comparison.
   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort
   */
  inline void sort();

  /**
   * Serializes the parameters to a query string (without leading '?').
   * @return The percent-encoded query string.
   * @see https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
   */
  inline std::string to_string() const;

  /**
   * Returns an iterator over all parameter keys.
   * Keys may repeat if there are duplicate parameters.
   * @return An iterator yielding string_view keys.
   * @note The iterator is invalidated if this object is modified.
   */
  inline url_search_params_keys_iter get_keys();

  /**
   * Returns an iterator over all parameter values.
   * @return An iterator yielding string_view values.
   * @note The iterator is invalidated if this object is modified.
   */
  inline url_search_params_values_iter get_values();

  /**
   * Returns an iterator over all key-value pairs.
   * @return An iterator yielding key-value pair views.
   * @note The iterator is invalidated if this object is modified.
   */
  inline url_search_params_entries_iter get_entries();

  /**
   * C++ style conventional iterator support. const only because we
   * do not really want the params to be modified via the iterator.
   */
  inline auto begin() const { return params.begin(); }
  inline auto end() const { return params.end(); }
  inline auto front() const { return params.front(); }
  inline auto back() const { return params.back(); }
  inline auto operator[](size_t index) const { return params[index]; }

  /**
   * @private
   * Used to reset the search params to a new input.
   * Used primarily for C API.
   * @param input
   */
  void reset(std::string_view input);

 private:
  typedef std::pair<std::string, std::string> key_value_pair;
  std::vector<key_value_pair> params{};

  /**
   * The init parameter must be valid UTF-8.
   * @see https://url.spec.whatwg.org/#concept-urlencoded-parser
   */
  void initialize(std::string_view init);

  template <typename T, url_search_params_iter_type Type>
  friend struct url_search_params_iter;
};  // url_search_params

/**
 * @brief JavaScript-style iterator for url_search_params.
 *
 * Provides a `next()` method that returns successive values until exhausted.
 * This matches the iterator pattern used in the Web Platform.
 *
 * @tparam T The type of value returned by the iterator.
 * @tparam Type The type of iteration (KEYS, VALUES, or ENTRIES).
 *
 * @see https://webidl.spec.whatwg.org/#idl-iterable
 */
template <typename T, url_search_params_iter_type Type>
struct url_search_params_iter {
  inline url_search_params_iter() : params(EMPTY) {}
  url_search_params_iter(const url_search_params_iter& u) = default;
  url_search_params_iter(url_search_params_iter&& u) noexcept = default;
  url_search_params_iter& operator=(url_search_params_iter&& u) noexcept =
      default;
  url_search_params_iter& operator=(const url_search_params_iter& u) = default;
  ~url_search_params_iter() = default;

  /**
   * Returns the next value in the iteration sequence.
   * @return The next value, or std::nullopt if iteration is complete.
   */
  inline std::optional<T> next();

  /**
   * Checks if more values are available.
   * @return `true` if `next()` will return a value, `false` if exhausted.
   */
  inline bool has_next() const;

 private:
  static url_search_params EMPTY;
  inline url_search_params_iter(url_search_params& params_) : params(params_) {}

  url_search_params& params;
  size_t pos = 0;

  friend struct url_search_params;
};

}  // namespace ada
#endif
/* end file include/ada/url_search_params.h */
/* begin file include/ada/url_search_params-inl.h */
/**
 * @file url_search_params-inl.h
 * @brief Inline declarations for the URL Search Params
 */
#ifndef ADA_URL_SEARCH_PARAMS_INL_H
#define ADA_URL_SEARCH_PARAMS_INL_H


#include <algorithm>
#include <cstdint>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>

namespace ada {

// Declared in implementation.h; used here as a DoS bound on untrusted query
// strings (ada.h includes both headers).
uint32_t get_max_input_length();

// A default, empty url_search_params for use with empty iterators.
template <typename T, ada::url_search_params_iter_type Type>
url_search_params url_search_params_iter<T, Type>::EMPTY;

inline void url_search_params::reset(std::string_view input) {
  params.clear();
  initialize(input);
}

inline void url_search_params::initialize(std::string_view input) {
  if (!input.empty() && input.front() == '?') {
    input.remove_prefix(1);
  }
  if (input.empty()) {
    return;
  }
  // Refuse overlong query strings (same process-wide cap as URL parsing).
  if (input.size() > get_max_input_length()) {
    return;
  }

  params.reserve(size_t(std::count(input.begin(), input.end(), '&')) + 1);

  auto process_key_value = [&](const std::string_view current) {
    const auto equal = current.find('=');
    if (equal == std::string_view::npos) {
      params.emplace_back(unicode::form_urlencoded_decode(current), "");
    } else {
      params.emplace_back(
          unicode::form_urlencoded_decode(current.substr(0, equal)),
          unicode::form_urlencoded_decode(current.substr(equal + 1)));
    }
  };

  while (!input.empty()) {
    const auto ampersand_index = input.find('&');

    if (ampersand_index == std::string_view::npos) {
      if (!input.empty()) {
        process_key_value(input);
      }
      break;
    } else if (ampersand_index != 0) {
      process_key_value(input.substr(0, ampersand_index));
    }

    input.remove_prefix(ampersand_index + 1);
  }
}

inline void url_search_params::append(const std::string_view key,
                                      const std::string_view value) {
  params.emplace_back(key, value);
}

inline size_t url_search_params::size() const noexcept { return params.size(); }

inline std::optional<std::string_view> url_search_params::get(
    const std::string_view key) {
  auto entry = std::ranges::find_if(
      params, [&key](const auto& param) { return param.first == key; });

  if (entry == params.end()) {
    return std::nullopt;
  }

  return entry->second;
}

inline std::vector<std::string> url_search_params::get_all(
    const std::string_view key) {
  std::vector<std::string> out{};

  for (auto& param : params) {
    if (param.first == key) {
      out.emplace_back(param.second);
    }
  }

  return out;
}

inline bool url_search_params::has(const std::string_view key) noexcept {
  auto entry = std::ranges::find_if(
      params, [&key](const auto& param) { return param.first == key; });
  return entry != params.end();
}

inline bool url_search_params::has(std::string_view key,
                                   std::string_view value) noexcept {
  auto entry = std::ranges::find_if(params, [&key, &value](const auto& param) {
    return param.first == key && param.second == value;
  });
  return entry != params.end();
}

inline std::string url_search_params::to_string() const {
  auto character_set = ada::character_sets::WWW_FORM_URLENCODED_PERCENT_ENCODE;
  std::string out{};
  for (size_t i = 0; i < params.size(); i++) {
    auto key = ada::unicode::percent_encode(params[i].first, character_set);
    auto value = ada::unicode::percent_encode(params[i].second, character_set);

    // Performance optimization: Move this inside percent_encode.
    std::ranges::replace(key, ' ', '+');
    std::ranges::replace(value, ' ', '+');

    if (i != 0) {
      out += "&";
    }
    out.append(key);
    out += "=";
    out.append(value);
  }
  return out;
}

inline void url_search_params::set(const std::string_view key,
                                   const std::string_view value) {
  const auto find = [&key](const auto& param) { return param.first == key; };

  auto it = std::ranges::find_if(params, find);

  if (it == params.end()) {
    params.emplace_back(key, value);
  } else {
    it->second = value;
    params.erase(std::remove_if(std::next(it), params.end(), find),
                 params.end());
  }
}

inline void url_search_params::remove(const std::string_view key) {
  std::erase_if(params,
                [&key](const auto& param) { return param.first == key; });
}

inline void url_search_params::remove(const std::string_view key,
                                      const std::string_view value) {
  std::erase_if(params, [&key, &value](const auto& param) {
    return param.first == key && param.second == value;
  });
}

inline void url_search_params::sort() {
  // Keys are expected to be valid UTF-8, but percent_decode can produce
  // arbitrary byte sequences. Handle truncated/invalid sequences gracefully.
  std::ranges::stable_sort(params, [](const key_value_pair& lhs,
                                      const key_value_pair& rhs) {
    size_t i = 0, j = 0;
    uint32_t low_surrogate1 = 0, low_surrogate2 = 0;
    while ((i < lhs.first.size() || low_surrogate1 != 0) &&
           (j < rhs.first.size() || low_surrogate2 != 0)) {
      uint32_t codePoint1 = 0, codePoint2 = 0;

      if (low_surrogate1 != 0) {
        codePoint1 = low_surrogate1;
        low_surrogate1 = 0;
      } else {
        uint8_t c1 = uint8_t(lhs.first[i]);
        if (c1 > 0x7F && c1 <= 0xDF && i + 1 < lhs.first.size()) {
          codePoint1 = ((c1 & 0x1F) << 6) | (uint8_t(lhs.first[i + 1]) & 0x3F);
          i += 2;
        } else if (c1 > 0xDF && c1 <= 0xEF && i + 2 < lhs.first.size()) {
          codePoint1 = ((c1 & 0x0F) << 12) |
                       ((uint8_t(lhs.first[i + 1]) & 0x3F) << 6) |
                       (uint8_t(lhs.first[i + 2]) & 0x3F);
          i += 3;
        } else if (c1 > 0xEF && c1 <= 0xF7 && i + 3 < lhs.first.size()) {
          codePoint1 = ((c1 & 0x07) << 18) |
                       ((uint8_t(lhs.first[i + 1]) & 0x3F) << 12) |
                       ((uint8_t(lhs.first[i + 2]) & 0x3F) << 6) |
                       (uint8_t(lhs.first[i + 3]) & 0x3F);
          i += 4;

          codePoint1 -= 0x10000;
          uint16_t high_surrogate = uint16_t(0xD800 + (codePoint1 >> 10));
          low_surrogate1 = uint16_t(0xDC00 + (codePoint1 & 0x3FF));
          codePoint1 = high_surrogate;
        } else {
          // ASCII (c1 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte
          codePoint1 = c1;
          i++;
        }
      }

      if (low_surrogate2 != 0) {
        codePoint2 = low_surrogate2;
        low_surrogate2 = 0;
      } else {
        uint8_t c2 = uint8_t(rhs.first[j]);
        if (c2 > 0x7F && c2 <= 0xDF && j + 1 < rhs.first.size()) {
          codePoint2 = ((c2 & 0x1F) << 6) | (uint8_t(rhs.first[j + 1]) & 0x3F);
          j += 2;
        } else if (c2 > 0xDF && c2 <= 0xEF && j + 2 < rhs.first.size()) {
          codePoint2 = ((c2 & 0x0F) << 12) |
                       ((uint8_t(rhs.first[j + 1]) & 0x3F) << 6) |
                       (uint8_t(rhs.first[j + 2]) & 0x3F);
          j += 3;
        } else if (c2 > 0xEF && c2 <= 0xF7 && j + 3 < rhs.first.size()) {
          codePoint2 = ((c2 & 0x07) << 18) |
                       ((uint8_t(rhs.first[j + 1]) & 0x3F) << 12) |
                       ((uint8_t(rhs.first[j + 2]) & 0x3F) << 6) |
                       (uint8_t(rhs.first[j + 3]) & 0x3F);
          j += 4;
          codePoint2 -= 0x10000;
          uint16_t high_surrogate = uint16_t(0xD800 + (codePoint2 >> 10));
          low_surrogate2 = uint16_t(0xDC00 + (codePoint2 & 0x3FF));
          codePoint2 = high_surrogate;
        } else {
          // ASCII (c2 <= 0x7F) or truncated/invalid UTF-8: treat as raw byte
          codePoint2 = c2;
          j++;
        }
      }

      if (codePoint1 != codePoint2) {
        return (codePoint1 < codePoint2);
      }
    }
    return (j < rhs.first.size() || low_surrogate2 != 0);
  });
}

inline url_search_params_keys_iter url_search_params::get_keys() {
  return url_search_params_keys_iter(*this);
}

/**
 * @see https://url.spec.whatwg.org/#interface-urlsearchparams
 */
inline url_search_params_values_iter url_search_params::get_values() {
  return url_search_params_values_iter(*this);
}

/**
 * @see https://url.spec.whatwg.org/#interface-urlsearchparams
 */
inline url_search_params_entries_iter url_search_params::get_entries() {
  return url_search_params_entries_iter(*this);
}

template <typename T, url_search_params_iter_type Type>
inline bool url_search_params_iter<T, Type>::has_next() const {
  return pos < params.params.size();
}

template <>
inline std::optional<std::string_view> url_search_params_keys_iter::next() {
  if (!has_next()) {
    return std::nullopt;
  }
  return params.params[pos++].first;
}

template <>
inline std::optional<std::string_view> url_search_params_values_iter::next() {
  if (!has_next()) {
    return std::nullopt;
  }
  return params.params[pos++].second;
}

template <>
inline std::optional<key_value_view_pair>
url_search_params_entries_iter::next() {
  if (!has_next()) {
    return std::nullopt;
  }
  return params.params[pos++];
}

}  // namespace ada

#endif  // ADA_URL_SEARCH_PARAMS_INL_H
/* end file include/ada/url_search_params-inl.h */

/* begin file include/ada/url_pattern-inl.h */
/**
 * @file url_pattern-inl.h
 * @brief Declaration for the URLPattern inline functions.
 */
#ifndef ADA_URL_PATTERN_INL_H
#define ADA_URL_PATTERN_INL_H


#include <algorithm>
#include <string_view>
#include <utility>

#if ADA_INCLUDE_URL_PATTERN
namespace ada {

inline bool url_pattern_init::operator==(const url_pattern_init& other) const {
  return protocol == other.protocol && username == other.username &&
         password == other.password && hostname == other.hostname &&
         port == other.port && search == other.search && hash == other.hash &&
         pathname == other.pathname;
}

inline bool url_pattern_component_result::operator==(
    const url_pattern_component_result& other) const {
  return input == other.input && groups == other.groups;
}

template <url_pattern_regex::regex_concept regex_provider>
url_pattern_component_result
url_pattern_component<regex_provider>::create_component_match_result(
    std::string&& input,
    std::vector<std::optional<std::string>>&& exec_result) {
  // Let result be a new URLPatternComponentResult.
  // Set result["input"] to input.
  // Let groups be a record<USVString, (USVString or undefined)>.
  auto result =
      url_pattern_component_result{.input = std::move(input), .groups = {}};

  // We explicitly start iterating from 0 even though the spec
  // says we should start from 1. This case is handled by the
  // std_regex_provider which removes the full match from index 0.
  // Use min() to guard against potential mismatches between
  // exec_result size and group_name_list size.
  const size_t size = std::min(exec_result.size(), group_name_list.size());
  result.groups.reserve(size);
  for (size_t index = 0; index < size; index++) {
    result.groups.emplace(group_name_list[index],
                          std::move(exec_result[index]));
  }
  return result;
}

template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_protocol() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's protocol component's pattern string.
  return protocol_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_username() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's username component's pattern string.
  return username_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_password() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's password component's pattern string.
  return password_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_hostname() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's hostname component's pattern string.
  return hostname_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_port() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's port component's pattern string.
  return port_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_pathname() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's pathname component's pattern string.
  return pathname_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_search() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's search component's pattern string.
  return search_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
std::string_view url_pattern<regex_provider>::get_hash() const
    ada_lifetime_bound {
  // Return this's associated URL pattern's hash component's pattern string.
  return hash_component.pattern;
}
template <url_pattern_regex::regex_concept regex_provider>
bool url_pattern<regex_provider>::ignore_case() const {
  return ignore_case_;
}
template <url_pattern_regex::regex_concept regex_provider>
bool url_pattern<regex_provider>::has_regexp_groups() const {
  // If this's associated URL pattern's has regexp groups, then return true.
  return protocol_component.has_regexp_groups ||
         username_component.has_regexp_groups ||
         password_component.has_regexp_groups ||
         hostname_component.has_regexp_groups ||
         port_component.has_regexp_groups ||
         pathname_component.has_regexp_groups ||
         search_component.has_regexp_groups || hash_component.has_regexp_groups;
}

inline bool url_pattern_part::is_regexp() const noexcept {
  return type == url_pattern_part_type::REGEXP;
}

inline std::string_view url_pattern_compile_component_options::get_delimiter()
    const {
  if (delimiter) {
    return {&delimiter.value(), 1};
  }
  return {};
}

inline std::string_view url_pattern_compile_component_options::get_prefix()
    const {
  if (prefix) {
    return {&prefix.value(), 1};
  }
  return {};
}

template <url_pattern_regex::regex_concept regex_provider>
template <url_pattern_encoding_callback F>
tl::expected<url_pattern_component<regex_provider>, errors>
url_pattern_component<regex_provider>::compile(
    std::string_view input, F& encoding_callback,
    url_pattern_compile_component_options& options) {
  ada_log("url_pattern_component::compile input: ", input);
  // Let part list be the result of running parse a pattern string given input,
  // options, and encoding callback.
  auto part_list = url_pattern_helpers::parse_pattern_string(input, options,
                                                             encoding_callback);

  if (!part_list) {
    ada_log("parse_pattern_string failed");
    return tl::unexpected(part_list.error());
  }

  // Detect pattern type early to potentially skip expensive regex compilation
  const auto has_regexp = [](const auto& part) { return part.is_regexp(); };
  const bool has_regexp_groups = std::ranges::any_of(*part_list, has_regexp);

  url_pattern_component_type component_type =
      url_pattern_component_type::REGEXP;
  std::string exact_match_value{};

  if (part_list->empty()) {
    component_type = url_pattern_component_type::EMPTY;
  } else if (part_list->size() == 1) {
    const auto& part = (*part_list)[0];
    if (part.type == url_pattern_part_type::FIXED_TEXT &&
        part.modifier == url_pattern_part_modifier::none &&
        !options.ignore_case) {
      component_type = url_pattern_component_type::EXACT_MATCH;
      exact_match_value = part.value;
    } else if (part.type == url_pattern_part_type::FULL_WILDCARD &&
               part.modifier == url_pattern_part_modifier::none &&
               part.prefix.empty() && part.suffix.empty()) {
      component_type = url_pattern_component_type::FULL_WILDCARD;
    }
  }

  // For simple patterns, skip regex generation and compilation entirely
  if (component_type != url_pattern_component_type::REGEXP) {
    auto pattern_string =
        url_pattern_helpers::generate_pattern_string(*part_list, options);
    // For FULL_WILDCARD, we need the group name from
    // generate_regular_expression
    std::vector<std::string> name_list;
    if (component_type == url_pattern_component_type::FULL_WILDCARD &&
        !part_list->empty()) {
      name_list.push_back((*part_list)[0].name);
    }
    return url_pattern_component<regex_provider>(
        std::move(pattern_string), typename regex_provider::regex_type{},
        std::move(name_list), has_regexp_groups, component_type,
        std::move(exact_match_value));
  }

  // Generate regex for complex patterns
  auto [regular_expression_string, name_list] =
      url_pattern_helpers::generate_regular_expression_and_name_list(*part_list,
                                                                     options);
  auto pattern_string =
      url_pattern_helpers::generate_pattern_string(*part_list, options);

  std::optional<typename regex_provider::regex_type> regular_expression =
      regex_provider::create_instance(regular_expression_string,
                                      options.ignore_case);
  if (!regular_expression) {
    return tl::unexpected(errors::type_error);
  }

  return url_pattern_component<regex_provider>(
      std::move(pattern_string), std::move(*regular_expression),
      std::move(name_list), has_regexp_groups, component_type,
      std::move(exact_match_value));
}

template <url_pattern_regex::regex_concept regex_provider>
bool url_pattern_component<regex_provider>::fast_test(
    std::string_view input) const noexcept {
  // Fast path for simple patterns - avoid regex evaluation
  // Using if-else for better branch prediction on common cases
  if (type == url_pattern_component_type::FULL_WILDCARD) {
    return true;
  }
  if (type == url_pattern_component_type::EXACT_MATCH) {
    return input == exact_match_value;
  }
  if (type == url_pattern_component_type::EMPTY) {
    return input.empty();
  }
  // type == REGEXP
  return regex_provider::regex_match(input, regexp);
}

template <url_pattern_regex::regex_concept regex_provider>
std::optional<std::vector<std::optional<std::string>>>
url_pattern_component<regex_provider>::fast_match(
    std::string_view input) const {
  // Handle each type directly without redundant checks
  if (type == url_pattern_component_type::FULL_WILDCARD) {
    // FULL_WILDCARD always matches - capture the input (even if empty)
    // If there's no group name, return empty groups
    if (group_name_list.empty()) {
      return std::vector<std::optional<std::string>>{};
    }
    // Capture the matched input (including empty strings)
    return std::vector<std::optional<std::string>>{std::string(input)};
  }
  if (type == url_pattern_component_type::EXACT_MATCH) {
    if (input == exact_match_value) {
      return std::vector<std::optional<std::string>>{};
    }
    return std::nullopt;
  }
  if (type == url_pattern_component_type::EMPTY) {
    if (input.empty()) {
      return std::vector<std::optional<std::string>>{};
    }
    return std::nullopt;
  }
  // type == REGEXP - use regex
  return regex_provider::regex_search(input, regexp);
}

template <url_pattern_regex::regex_concept regex_provider>
result<std::optional<url_pattern_result>> url_pattern<regex_provider>::exec(
    const url_pattern_input& input, const std::string_view* base_url) {
  // Return the result of match given this's associated URL pattern, input, and
  // baseURL if given.
  return match(input, base_url);
}

template <url_pattern_regex::regex_concept regex_provider>
bool url_pattern<regex_provider>::test_components(
    std::string_view protocol, std::string_view username,
    std::string_view password, std::string_view hostname, std::string_view port,
    std::string_view pathname, std::string_view search,
    std::string_view hash) const {
  return protocol_component.fast_test(protocol) &&
         username_component.fast_test(username) &&
         password_component.fast_test(password) &&
         hostname_component.fast_test(hostname) &&
         port_component.fast_test(port) &&
         pathname_component.fast_test(pathname) &&
         search_component.fast_test(search) && hash_component.fast_test(hash);
}

template <url_pattern_regex::regex_concept regex_provider>
result<bool> url_pattern<regex_provider>::test(
    const url_pattern_input& input, const std::string_view* base_url_string) {
  // If input is a URLPatternInit
  if (std::holds_alternative<url_pattern_init>(input)) {
    if (base_url_string) {
      return tl::unexpected(errors::type_error);
    }

    std::string protocol{}, username{}, password{}, hostname{};
    std::string port{}, pathname{}, search{}, hash{};

    auto apply_result = url_pattern_init::process(
        std::get<url_pattern_init>(input), url_pattern_init::process_type::url,
        protocol, username, password, hostname, port, pathname, search, hash);

    if (!apply_result) {
      return false;
    }

    std::string_view search_view = *apply_result->search;
    if (search_view.starts_with("?")) {
      search_view.remove_prefix(1);
    }

    return test_components(*apply_result->protocol, *apply_result->username,
                           *apply_result->password, *apply_result->hostname,
                           *apply_result->port, *apply_result->pathname,
                           search_view, *apply_result->hash);
  }

  // URL string input path
  result<url_aggregator> base_url;
  if (base_url_string) {
    base_url = ada::parse<url_aggregator>(*base_url_string, nullptr);
    if (!base_url) {
      return false;
    }
  }

  auto url =
      ada::parse<url_aggregator>(std::get<std::string_view>(input),
                                 base_url.has_value() ? &*base_url : nullptr);
  if (!url) {
    return false;
  }

  // Extract components as string_view
  auto protocol_view = url->get_protocol();
  if (protocol_view.ends_with(":")) {
    protocol_view.remove_suffix(1);
  }

  auto search_view = url->get_search();
  if (search_view.starts_with("?")) {
    search_view.remove_prefix(1);
  }

  auto hash_view = url->get_hash();
  if (hash_view.starts_with("#")) {
    hash_view.remove_prefix(1);
  }

  return test_components(protocol_view, url->get_username(),
                         url->get_password(), url->get_hostname(),
                         url->get_port(), url->get_pathname(), search_view,
                         hash_view);
}

template <url_pattern_regex::regex_concept regex_provider>
result<std::optional<url_pattern_result>> url_pattern<regex_provider>::match(
    const url_pattern_input& input, const std::string_view* base_url_string) {
  std::string protocol{};
  std::string username{};
  std::string password{};
  std::string hostname{};
  std::string port{};
  std::string pathname{};
  std::string search{};
  std::string hash{};

  // Let inputs be an empty list.
  // Append input to inputs.
  std::vector inputs{input};

  // If input is a URLPatternInit then:
  if (std::holds_alternative<url_pattern_init>(input)) {
    ada_log(
        "url_pattern::match called with url_pattern_init and base_url_string=",
        base_url_string);
    // If baseURLString was given, throw a TypeError.
    if (base_url_string) {
      ada_log("failed to match because base_url_string was given");
      return tl::unexpected(errors::type_error);
    }

    // Let applyResult be the result of process a URLPatternInit given input,
    // "url", protocol, username, password, hostname, port, pathname, search,
    // and hash.
    auto apply_result = url_pattern_init::process(
        std::get<url_pattern_init>(input), url_pattern_init::process_type::url,
        protocol, username, password, hostname, port, pathname, search, hash);

    // If this throws an exception, catch it, and return null.
    if (!apply_result.has_value()) {
      ada_log("match returned std::nullopt because process threw");
      return std::nullopt;
    }

    // Set protocol to applyResult["protocol"].
    ADA_ASSERT_TRUE(apply_result->protocol.has_value());
    protocol = std::move(apply_result->protocol.value());

    // Set username to applyResult["username"].
    ADA_ASSERT_TRUE(apply_result->username.has_value());
    username = std::move(apply_result->username.value());

    // Set password to applyResult["password"].
    ADA_ASSERT_TRUE(apply_result->password.has_value());
    password = std::move(apply_result->password.value());

    // Set hostname to applyResult["hostname"].
    ADA_ASSERT_TRUE(apply_result->hostname.has_value());
    hostname = std::move(apply_result->hostname.value());

    // Set port to applyResult["port"].
    ADA_ASSERT_TRUE(apply_result->port.has_value());
    port = std::move(apply_result->port.value());

    // Set pathname to applyResult["pathname"].
    ADA_ASSERT_TRUE(apply_result->pathname.has_value());
    pathname = std::move(apply_result->pathname.value());

    // Set search to applyResult["search"].
    ADA_ASSERT_TRUE(apply_result->search.has_value());
    if (apply_result->search->starts_with("?")) {
      search = apply_result->search->substr(1);
    } else {
      search = std::move(apply_result->search.value());
    }

    // Set hash to applyResult["hash"].
    ADA_ASSERT_TRUE(apply_result->hash.has_value());
    ADA_ASSERT_TRUE(!apply_result->hash->starts_with("#"));
    hash = std::move(apply_result->hash.value());
  } else {
    ADA_ASSERT_TRUE(std::holds_alternative<std::string_view>(input));

    // Let baseURL be null.
    result<url_aggregator> base_url;

    // If baseURLString was given, then:
    if (base_url_string) {
      // Let baseURL be the result of parsing baseURLString.
      base_url = ada::parse<url_aggregator>(*base_url_string, nullptr);

      // If baseURL is failure, return null.
      if (!base_url) {
        ada_log("match returned std::nullopt because failed to parse base_url=",
                *base_url_string);
        return std::nullopt;
      }

      // Append baseURLString to inputs.
      inputs.emplace_back(*base_url_string);
    }

    url_aggregator* base_url_value =
        base_url.has_value() ? &*base_url : nullptr;

    // Set url to the result of parsing input given baseURL.
    auto url = ada::parse<url_aggregator>(std::get<std::string_view>(input),
                                          base_url_value);

    // If url is failure, return null.
    if (!url) {
      ada_log("match returned std::nullopt because url failed");
      return std::nullopt;
    }

    // Set protocol to url's scheme.
    // IMPORTANT: Not documented on the URLPattern spec, but protocol suffix ':'
    // is removed. Similar work was done on workerd:
    // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2038
    protocol = url->get_protocol().substr(0, url->get_protocol().size() - 1);
    // Set username to url's username.
    username = url->get_username();
    // Set password to url's password.
    password = url->get_password();
    // Set hostname to url's host, serialized, or the empty string if the value
    // is null.
    hostname = url->get_hostname();
    // Set port to url's port, serialized, or the empty string if the value is
    // null.
    port = url->get_port();
    // Set pathname to the result of URL path serializing url.
    pathname = url->get_pathname();
    // Set search to url's query or the empty string if the value is null.
    // IMPORTANT: Not documented on the URLPattern spec, but search prefix '?'
    // is removed. Similar work was done on workerd:
    // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2232
    if (url->has_search()) {
      auto view = url->get_search();
      search = view.starts_with("?") ? url->get_search().substr(1) : view;
    }
    // Set hash to url's fragment or the empty string if the value is null.
    // IMPORTANT: Not documented on the URLPattern spec, but hash prefix '#' is
    // removed. Similar work was done on workerd:
    // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2242
    if (url->has_hash()) {
      auto view = url->get_hash();
      hash = view.starts_with("#") ? url->get_hash().substr(1) : view;
    }
  }

  // Use fast_match which skips regex for simple patterns (EMPTY, EXACT_MATCH,
  // FULL_WILDCARD) and only falls back to regex for complex REGEXP patterns.

  // Let protocolExecResult be RegExpBuiltinExec(urlPattern's protocol
  // component's regular expression, protocol).
  auto protocol_exec_result = protocol_component.fast_match(protocol);
  if (!protocol_exec_result) {
    return std::nullopt;
  }

  // Let usernameExecResult be RegExpBuiltinExec(urlPattern's username
  // component's regular expression, username).
  auto username_exec_result = username_component.fast_match(username);
  if (!username_exec_result) {
    return std::nullopt;
  }

  // Let passwordExecResult be RegExpBuiltinExec(urlPattern's password
  // component's regular expression, password).
  auto password_exec_result = password_component.fast_match(password);
  if (!password_exec_result) {
    return std::nullopt;
  }

  // Let hostnameExecResult be RegExpBuiltinExec(urlPattern's hostname
  // component's regular expression, hostname).
  auto hostname_exec_result = hostname_component.fast_match(hostname);
  if (!hostname_exec_result) {
    return std::nullopt;
  }

  // Let portExecResult be RegExpBuiltinExec(urlPattern's port component's
  // regular expression, port).
  auto port_exec_result = port_component.fast_match(port);
  if (!port_exec_result) {
    return std::nullopt;
  }

  // Let pathnameExecResult be RegExpBuiltinExec(urlPattern's pathname
  // component's regular expression, pathname).
  auto pathname_exec_result = pathname_component.fast_match(pathname);
  if (!pathname_exec_result) {
    return std::nullopt;
  }

  // Let searchExecResult be RegExpBuiltinExec(urlPattern's search component's
  // regular expression, search).
  auto search_exec_result = search_component.fast_match(search);
  if (!search_exec_result) {
    return std::nullopt;
  }

  // Let hashExecResult be RegExpBuiltinExec(urlPattern's hash component's
  // regular expression, hash).
  auto hash_exec_result = hash_component.fast_match(hash);
  if (!hash_exec_result) {
    return std::nullopt;
  }

  // Let result be a new URLPatternResult.
  auto result = url_pattern_result{};
  // Set result["inputs"] to inputs.
  result.inputs = std::move(inputs);
  // Set result["protocol"] to the result of creating a component match result
  // given urlPattern's protocol component, protocol, and protocolExecResult.
  result.protocol = protocol_component.create_component_match_result(
      std::move(protocol), std::move(*protocol_exec_result));

  // Set result["username"] to the result of creating a component match result
  // given urlPattern's username component, username, and usernameExecResult.
  result.username = username_component.create_component_match_result(
      std::move(username), std::move(*username_exec_result));

  // Set result["password"] to the result of creating a component match result
  // given urlPattern's password component, password, and passwordExecResult.
  result.password = password_component.create_component_match_result(
      std::move(password), std::move(*password_exec_result));

  // Set result["hostname"] to the result of creating a component match result
  // given urlPattern's hostname component, hostname, and hostnameExecResult.
  result.hostname = hostname_component.create_component_match_result(
      std::move(hostname), std::move(*hostname_exec_result));

  // Set result["port"] to the result of creating a component match result given
  // urlPattern's port component, port, and portExecResult.
  result.port = port_component.create_component_match_result(
      std::move(port), std::move(*port_exec_result));

  // Set result["pathname"] to the result of creating a component match result
  // given urlPattern's pathname component, pathname, and pathnameExecResult.
  result.pathname = pathname_component.create_component_match_result(
      std::move(pathname), std::move(*pathname_exec_result));

  // Set result["search"] to the result of creating a component match result
  // given urlPattern's search component, search, and searchExecResult.
  result.search = search_component.create_component_match_result(
      std::move(search), std::move(*search_exec_result));

  // Set result["hash"] to the result of creating a component match result given
  // urlPattern's hash component, hash, and hashExecResult.
  result.hash = hash_component.create_component_match_result(
      std::move(hash), std::move(*hash_exec_result));

  return result;
}

}  // namespace ada
#endif  // ADA_INCLUDE_URL_PATTERN
#endif
/* end file include/ada/url_pattern-inl.h */
/* begin file include/ada/url_pattern_helpers-inl.h */
/**
 * @file url_pattern_helpers-inl.h
 * @brief Declaration for the URLPattern helpers.
 */
#ifndef ADA_URL_PATTERN_HELPERS_INL_H
#define ADA_URL_PATTERN_HELPERS_INL_H

#include <optional>
#include <string_view>


#if ADA_INCLUDE_URL_PATTERN
namespace ada::url_pattern_helpers {
#if defined(ADA_TESTING) || defined(ADA_LOGGING)
inline std::string to_string(token_type type) {
  switch (type) {
    case token_type::INVALID_CHAR:
      return "INVALID_CHAR";
    case token_type::OPEN:
      return "OPEN";
    case token_type::CLOSE:
      return "CLOSE";
    case token_type::REGEXP:
      return "REGEXP";
    case token_type::NAME:
      return "NAME";
    case token_type::CHAR:
      return "CHAR";
    case token_type::ESCAPED_CHAR:
      return "ESCAPED_CHAR";
    case token_type::OTHER_MODIFIER:
      return "OTHER_MODIFIER";
    case token_type::ASTERISK:
      return "ASTERISK";
    case token_type::END:
      return "END";
    default:
      ada::unreachable();
  }
}
#endif  // defined(ADA_TESTING) || defined(ADA_LOGGING)

template <url_pattern_regex::regex_concept regex_provider>
constexpr void constructor_string_parser<regex_provider>::rewind() {
  // Set parser's token index to parser's component start.
  token_index = component_start;
  // Set parser's token increment to 0.
  token_increment = 0;
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_hash_prefix() {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index and "#".
  return is_non_special_pattern_char(token_index, '#');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_search_prefix() {
  // If result of running is a non-special pattern char given parser, parser's
  // token index and "?" is true, then return true.
  if (is_non_special_pattern_char(token_index, '?')) {
    return true;
  }

  // If parser's token list[parser's token index]'s value is not "?", then
  // return false.
  if (token_list[token_index].value != "?") {
    return false;
  }

  // If previous index is less than 0, then return true.
  if (token_index == 0) return true;
  // Let previous index be parser's token index - 1.
  auto previous_index = token_index - 1;
  // Let previous token be the result of running get a safe token given parser
  // and previous index.
  auto previous_token = get_safe_token(previous_index);
  ADA_ASSERT_TRUE(previous_token);
  // If any of the following are true, then return false:
  // - previous token's type is "name".
  // - previous token's type is "regexp".
  // - previous token's type is "close".
  // - previous token's type is "asterisk".
  return !(previous_token->type == token_type::NAME ||
           previous_token->type == token_type::REGEXP ||
           previous_token->type == token_type::CLOSE ||
           previous_token->type == token_type::ASTERISK);
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool
constructor_string_parser<regex_provider>::is_non_special_pattern_char(
    size_t index, uint32_t value) const {
  // Let token be the result of running get a safe token given parser and index.
  auto token = get_safe_token(index);
  ADA_ASSERT_TRUE(token);

  // If token's value is not value, then return false.
  // TODO: Remove this once we make sure get_safe_token returns a non-empty
  // string.
  if (!token->value.empty() &&
      static_cast<uint32_t>(token->value[0]) != value) {
    return false;
  }

  // If any of the following are true:
  // - token's type is "char";
  // - token's type is "escaped-char"; or
  // - token's type is "invalid-char",
  // - then return true.
  return token->type == token_type::CHAR ||
         token->type == token_type::ESCAPED_CHAR ||
         token->type == token_type::INVALID_CHAR;
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr const token*
constructor_string_parser<regex_provider>::get_safe_token(size_t index) const {
  // If index is less than parser's token list's size, then return parser's
  // token list[index].
  if (index < token_list.size()) [[likely]] {
    return &token_list[index];
  }

  // Assert: parser's token list's size is greater than or equal to 1.
  ADA_ASSERT_TRUE(!token_list.empty());

  // Let token be parser's token list[last index].
  // Assert: token's type is "end".
  ADA_ASSERT_TRUE(token_list.back().type == token_type::END);

  // Return token.
  return &token_list.back();
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_group_open()
    const {
  // If parser's token list[parser's token index]'s type is "open", then return
  // true.
  return token_list[token_index].type == token_type::OPEN;
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_group_close()
    const {
  // If parser's token list[parser's token index]'s type is "close", then return
  // true.
  return token_list[token_index].type == token_type::CLOSE;
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool
constructor_string_parser<regex_provider>::next_is_authority_slashes() const {
  // If the result of running is a non-special pattern char given parser,
  // parser's token index + 1, and "/" is false, then return false.
  if (!is_non_special_pattern_char(token_index + 1, '/')) {
    return false;
  }
  // If the result of running is a non-special pattern char given parser,
  // parser's token index + 2, and "/" is false, then return false.
  if (!is_non_special_pattern_char(token_index + 2, '/')) {
    return false;
  }
  return true;
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_protocol_suffix()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and ":".
  return is_non_special_pattern_char(token_index, ':');
}

template <url_pattern_regex::regex_concept regex_provider>
void constructor_string_parser<regex_provider>::change_state(State new_state,
                                                             size_t skip) {
  // If parser's state is not "init", not "authority", and not "done", then set
  // parser's result[parser's state] to the result of running make a component
  // string given parser.
  if (state != State::INIT && state != State::AUTHORITY &&
      state != State::DONE) {
    auto value = make_component_string();
    // TODO: Simplify this.
    switch (state) {
      case State::PROTOCOL: {
        result.protocol = value;
        break;
      }
      case State::USERNAME: {
        result.username = value;
        break;
      }
      case State::PASSWORD: {
        result.password = value;
        break;
      }
      case State::HOSTNAME: {
        result.hostname = value;
        break;
      }
      case State::PORT: {
        result.port = value;
        break;
      }
      case State::PATHNAME: {
        result.pathname = value;
        break;
      }
      case State::SEARCH: {
        result.search = value;
        break;
      }
      case State::HASH: {
        result.hash = value;
        break;
      }
      default:
        ada::unreachable();
    }
  }

  // If parser's state is not "init" and new state is not "done", then:
  if (state != State::INIT && new_state != State::DONE) {
    // If parser's state is "protocol", "authority", "username", or "password";
    // new state is "port", "pathname", "search", or "hash"; and parser's
    // result["hostname"] does not exist, then set parser's result["hostname"]
    // to the empty string.
    if ((state == State::PROTOCOL || state == State::AUTHORITY ||
         state == State::USERNAME || state == State::PASSWORD) &&
        (new_state == State::PORT || new_state == State::PATHNAME ||
         new_state == State::SEARCH || new_state == State::HASH) &&
        !result.hostname)
      result.hostname = "";
  }

  // If parser's state is "protocol", "authority", "username", "password",
  // "hostname", or "port"; new state is "search" or "hash"; and parser's
  // result["pathname"] does not exist, then:
  if ((state == State::PROTOCOL || state == State::AUTHORITY ||
       state == State::USERNAME || state == State::PASSWORD ||
       state == State::HOSTNAME || state == State::PORT) &&
      (new_state == State::SEARCH || new_state == State::HASH) &&
      !result.pathname) {
    if (protocol_matches_a_special_scheme_flag) {
      result.pathname = "/";
    } else {
      // Otherwise, set parser's result["pathname"] to the empty string.
      result.pathname = "";
    }
  }

  // If parser's state is "protocol", "authority", "username", "password",
  // "hostname", "port", or "pathname"; new state is "hash"; and parser's
  // result["search"] does not exist, then set parser's result["search"] to
  // the empty string.
  if ((state == State::PROTOCOL || state == State::AUTHORITY ||
       state == State::USERNAME || state == State::PASSWORD ||
       state == State::HOSTNAME || state == State::PORT ||
       state == State::PATHNAME) &&
      new_state == State::HASH && !result.search) {
    result.search = "";
  }

  // Set parser's state to new state.
  state = new_state;
  // Increment parser's token index by skip.
  token_index += skip;
  // Set parser's component start to parser's token index.
  component_start = token_index;
  // Set parser's token increment to 0.
  token_increment = 0;
}

template <url_pattern_regex::regex_concept regex_provider>
std::string constructor_string_parser<regex_provider>::make_component_string() {
  // Assert: parser's token index is less than parser's token list's size.
  ADA_ASSERT_TRUE(token_index < token_list.size());

  // Let token be parser's token list[parser's token index].
  // Let end index be token's index.
  const auto end_index = token_list[token_index].index;
  // Let component start token be the result of running get a safe token given
  // parser and parser's component start.
  const auto component_start_token = get_safe_token(component_start);
  ADA_ASSERT_TRUE(component_start_token);
  // Let component start input index be component start token's index.
  const auto component_start_input_index = component_start_token->index;
  // Return the code point substring from component start input index to end
  // index within parser's input.
  return std::string(input.substr(component_start_input_index,
                                  end_index - component_start_input_index));
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool
constructor_string_parser<regex_provider>::is_an_identity_terminator() const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and "@".
  return is_non_special_pattern_char(token_index, '@');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_pathname_start()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and "/".
  return is_non_special_pattern_char(token_index, '/');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_password_prefix()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and ":".
  return is_non_special_pattern_char(token_index, ':');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_an_ipv6_open()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and "[".
  return is_non_special_pattern_char(token_index, '[');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_an_ipv6_close()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and "]".
  return is_non_special_pattern_char(token_index, ']');
}

template <url_pattern_regex::regex_concept regex_provider>
constexpr bool constructor_string_parser<regex_provider>::is_port_prefix()
    const {
  // Return the result of running is a non-special pattern char given parser,
  // parser's token index, and ":".
  return is_non_special_pattern_char(token_index, ':');
}

constexpr void Tokenizer::get_next_code_point() {
  ada_log("Tokenizer::get_next_code_point called with index=", next_index);
  ADA_ASSERT_TRUE(next_index < input.size());
  // Decode the next UTF-8 code point. If malformed or truncated, mark it as
  // invalid, return the offending byte as the code point, and advance by one
  // to guarantee forward progress.
  invalid_code_point = false;
  code_point = 0;
  size_t number_bytes = 0;
  const size_t initial_index = next_index;
  unsigned char first_byte = input[next_index];

  if ((first_byte & 0x80) == 0) {
    // 1-byte character (ASCII)
    next_index++;
    code_point = first_byte;
    ada_log("Tokenizer::get_next_code_point returning ASCII code point=",
            uint32_t(code_point));
    ada_log("Tokenizer::get_next_code_point next_index =", next_index,
            " input.size()=", input.size());
    return;
  }
  ada_log("Tokenizer::get_next_code_point read first byte=",
          uint32_t(first_byte));
  if ((first_byte & 0xE0) == 0xC0) {
    code_point = first_byte & 0x1F;
    number_bytes = 2;
    ada_log("Tokenizer::get_next_code_point two bytes");
  } else if ((first_byte & 0xF0) == 0xE0) {
    code_point = first_byte & 0x0F;
    number_bytes = 3;
    ada_log("Tokenizer::get_next_code_point three bytes");
  } else if ((first_byte & 0xF8) == 0xF0) {
    code_point = first_byte & 0x07;
    number_bytes = 4;
    ada_log("Tokenizer::get_next_code_point four bytes");
  }

  // Invalid leading bytes that still match a multi-byte prefix.
  if ((number_bytes == 2 && first_byte < 0xC2) ||
      (number_bytes == 4 && first_byte > 0xF4)) {
    invalid_code_point = true;
    code_point = first_byte;
    next_index = initial_index + 1;
    return;
  }

  // Invalid leading byte (e.g., continuation byte outside a sequence).
  if (number_bytes == 0) {
    invalid_code_point = true;
    code_point = first_byte;
    next_index = initial_index + 1;
    return;
  }

  // Truncated UTF-8 sequence.
  if (number_bytes + next_index > input.size()) {
    invalid_code_point = true;
    code_point = first_byte;
    next_index = initial_index + 1;
    return;
  }

  for (size_t i = 1 + next_index; i < number_bytes + next_index; ++i) {
    unsigned char byte = input[i];
    if ((byte & 0xC0) != 0x80) {
      invalid_code_point = true;
      code_point = first_byte;
      next_index = initial_index + 1;
      return;
    }
    ada_log("Tokenizer::get_next_code_point read byte=", uint32_t(byte));
    code_point = (code_point << 6) | (byte & 0x3F);
  }
  ada_log("Tokenizer::get_next_code_point returning non-ASCII code point=",
          uint32_t(code_point));
  ada_log("Tokenizer::get_next_code_point next_index =", next_index,
          " input.size()=", input.size());
  next_index += number_bytes;
}

constexpr void Tokenizer::seek_and_get_next_code_point(size_t new_index) {
  ada_log("Tokenizer::seek_and_get_next_code_point called with new_index=",
          new_index);
  // Set tokenizer's next index to index.
  next_index = new_index;
  // Run get the next code point given tokenizer.
  get_next_code_point();
}

inline void Tokenizer::add_token(token_type type, size_t next_position,
                                 size_t value_position, size_t value_length) {
  ada_log("Tokenizer::add_token called with type=", to_string(type),
          " next_position=", next_position, " value_position=", value_position);
  ADA_ASSERT_TRUE(next_position >= value_position);

  // Let token be a new token.
  // Set token's type to type.
  // Set token's index to tokenizer's index.
  // Set token's value to the code point substring from value position with
  // length value length within tokenizer's input.
  // Append token to the back of tokenizer's token list.
  token_list.emplace_back(type, index,
                          input.substr(value_position, value_length));
  // Set tokenizer's index to next position.
  index = next_position;
}

inline void Tokenizer::add_token_with_default_length(token_type type,
                                                     size_t next_position,
                                                     size_t value_position) {
  // Let computed length be next position - value position.
  auto computed_length = next_position - value_position;
  // Run add a token given tokenizer, type, next position, value position, and
  // computed length.
  add_token(type, next_position, value_position, computed_length);
}

inline void Tokenizer::add_token_with_defaults(token_type type) {
  ada_log("Tokenizer::add_token_with_defaults called with type=",
          to_string(type));
  // Run add a token with default length given tokenizer, type, tokenizer's next
  // index, and tokenizer's index.
  add_token_with_default_length(type, next_index, index);
}

inline ada_warn_unused std::optional<errors>
Tokenizer::process_tokenizing_error(size_t next_position,
                                    size_t value_position) {
  // If tokenizer's policy is "strict", then throw a TypeError.
  if (policy == token_policy::strict) {
    ada_log("process_tokenizing_error failed with next_position=",
            next_position, " value_position=", value_position);
    return errors::type_error;
  }
  // Assert: tokenizer's policy is "lenient".
  ADA_ASSERT_TRUE(policy == token_policy::lenient);
  // Run add a token with default length given tokenizer, "invalid-char", next
  // position, and value position.
  add_token_with_default_length(token_type::INVALID_CHAR, next_position,
                                value_position);
  return std::nullopt;
}

template <url_pattern_encoding_callback F>
token* url_pattern_parser<F>::try_consume_modifier_token() {
  // Let token be the result of running try to consume a token given parser and
  // "other-modifier".
  auto token = try_consume_token(token_type::OTHER_MODIFIER);
  // If token is not null, then return token.
  if (token) return token;
  // Set token to the result of running try to consume a token given parser and
  // "asterisk".
  // Return token.
  return try_consume_token(token_type::ASTERISK);
}

template <url_pattern_encoding_callback F>
token* url_pattern_parser<F>::try_consume_regexp_or_wildcard_token(
    const token* name_token) {
  // Let token be the result of running try to consume a token given parser and
  // "regexp".
  auto token = try_consume_token(token_type::REGEXP);
  // If name token is null and token is null, then set token to the result of
  // running try to consume a token given parser and "asterisk".
  if (!name_token && !token) {
    token = try_consume_token(token_type::ASTERISK);
  }
  // Return token.
  return token;
}

template <url_pattern_encoding_callback F>
token* url_pattern_parser<F>::try_consume_token(token_type type) {
  ada_log("url_pattern_parser::try_consume_token called with type=",
          to_string(type));
  // Assert: parser's index is less than parser's token list size.
  ADA_ASSERT_TRUE(index < tokens.size());
  // Let next token be parser's token list[parser's index].
  auto& next_token = tokens[index];
  // If next token's type is not type return null.
  if (next_token.type != type) return nullptr;
  // Increase parser's index by 1.
  index++;
  // Return next token.
  return &next_token;
}

template <url_pattern_encoding_callback F>
std::string url_pattern_parser<F>::consume_text() {
  // Let result be the empty string.
  std::string result{};
  // While true:
  while (true) {
    // Let token be the result of running try to consume a token given parser
    // and "char".
    auto token = try_consume_token(token_type::CHAR);
    // If token is null, then set token to the result of running try to consume
    // a token given parser and "escaped-char".
    if (!token) token = try_consume_token(token_type::ESCAPED_CHAR);
    // If token is null, then break.
    if (!token) break;
    // Append token's value to the end of result.
    result.append(token->value);
  }
  // Return result.
  return result;
}

template <url_pattern_encoding_callback F>
bool url_pattern_parser<F>::consume_required_token(token_type type) {
  ada_log("url_pattern_parser::consume_required_token called with type=",
          to_string(type));
  // Let result be the result of running try to consume a token given parser and
  // type.
  return try_consume_token(type) != nullptr;
}

template <url_pattern_encoding_callback F>
std::optional<errors>
url_pattern_parser<F>::maybe_add_part_from_the_pending_fixed_value() {
  // If parser's pending fixed value is the empty string, then return.
  if (pending_fixed_value.empty()) {
    ada_log("pending_fixed_value is empty");
    return std::nullopt;
  }
  // Let encoded value be the result of running parser's encoding callback given
  // parser's pending fixed value.
  auto encoded_value = encoding_callback(pending_fixed_value);
  if (!encoded_value) {
    ada_log("failed to encode pending_fixed_value: ", pending_fixed_value);
    return encoded_value.error();
  }
  // Set parser's pending fixed value to the empty string.
  pending_fixed_value.clear();
  // Let part be a new part whose type is "fixed-text", value is encoded value,
  // and modifier is "none".
  // Append part to parser's part list.
  parts.emplace_back(url_pattern_part_type::FIXED_TEXT,
                     std::move(*encoded_value),
                     url_pattern_part_modifier::none);
  return std::nullopt;
}

template <url_pattern_encoding_callback F>
std::optional<errors> url_pattern_parser<F>::add_part(
    std::string_view prefix, token* name_token, token* regexp_or_wildcard_token,
    std::string_view suffix, token* modifier_token) {
  // Let modifier be "none".
  auto modifier = url_pattern_part_modifier::none;
  // If modifier token is not null:
  if (modifier_token) {
    // If modifier token's value is "?" then set modifier to "optional".
    if (modifier_token->value == "?") {
      modifier = url_pattern_part_modifier::optional;
    } else if (modifier_token->value == "*") {
      // Otherwise if modifier token's value is "*" then set modifier to
      // "zero-or-more".
      modifier = url_pattern_part_modifier::zero_or_more;
    } else if (modifier_token->value == "+") {
      // Otherwise if modifier token's value is "+" then set modifier to
      // "one-or-more".
      modifier = url_pattern_part_modifier::one_or_more;
    }
  }
  // If name token is null and regexp or wildcard token is null and modifier
  // is "none":
  if (!name_token && !regexp_or_wildcard_token &&
      modifier == url_pattern_part_modifier::none) {
    // Append prefix to the end of parser's pending fixed value.
    pending_fixed_value.append(prefix);
    return std::nullopt;
  }
  // Run maybe add a part from the pending fixed value given parser.
  if (auto error = maybe_add_part_from_the_pending_fixed_value()) {
    return *error;
  }
  // If name token is null and regexp or wildcard token is null:
  if (!name_token && !regexp_or_wildcard_token) {
    // Assert: suffix is the empty string.
    ADA_ASSERT_TRUE(suffix.empty());
    // If prefix is the empty string, then return.
    if (prefix.empty()) return std::nullopt;
    // Let encoded value be the result of running parser's encoding callback
    // given prefix.
    auto encoded_value = encoding_callback(prefix);
    if (!encoded_value) {
      return encoded_value.error();
    }
    // Let part be a new part whose type is "fixed-text", value is encoded
    // value, and modifier is modifier.
    // Append part to parser's part list.
    parts.emplace_back(url_pattern_part_type::FIXED_TEXT,
                       std::move(*encoded_value), modifier);
    return std::nullopt;
  }
  // Let regexp value be the empty string.
  std::string regexp_value{};
  // If regexp or wildcard token is null, then set regexp value to parser's
  // segment wildcard regexp.
  if (!regexp_or_wildcard_token) {
    regexp_value = segment_wildcard_regexp;
  } else if (regexp_or_wildcard_token->type == token_type::ASTERISK) {
    // Otherwise if regexp or wildcard token's type is "asterisk", then set
    // regexp value to the full wildcard regexp value.
    regexp_value = ".*";
  } else {
    // Otherwise set regexp value to regexp or wildcard token's value.
    regexp_value = regexp_or_wildcard_token->value;
  }
  // Let type be "regexp".
  auto type = url_pattern_part_type::REGEXP;
  // If regexp value is parser's segment wildcard regexp:
  if (regexp_value == segment_wildcard_regexp) {
    // Set type to "segment-wildcard".
    type = url_pattern_part_type::SEGMENT_WILDCARD;
    // Set regexp value to the empty string.
    regexp_value.clear();
  } else if (regexp_value == ".*") {
    // Otherwise if regexp value is the full wildcard regexp value:
    // Set type to "full-wildcard".
    type = url_pattern_part_type::FULL_WILDCARD;
    // Set regexp value to the empty string.
    regexp_value.clear();
  }
  // Let name be the empty string.
  std::string name{};
  // If name token is not null, then set name to name token's value.
  if (name_token) {
    name = name_token->value;
  } else if (regexp_or_wildcard_token != nullptr) {
    // Otherwise if regexp or wildcard token is not null:
    // Set name to parser's next numeric name, serialized.
    name = std::to_string(next_numeric_name);
    // Increment parser's next numeric name by 1.
    next_numeric_name++;
  }
  // If the result of running is a duplicate name given parser and name is
  // true, then throw a TypeError.
  if (std::ranges::any_of(
          parts, [&name](const auto& part) { return part.name == name; })) {
    return errors::type_error;
  }
  // Let encoded prefix be the result of running parser's encoding callback
  // given prefix.
  auto encoded_prefix = encoding_callback(prefix);
  if (!encoded_prefix) return encoded_prefix.error();
  // Let encoded suffix be the result of running parser's encoding callback
  // given suffix.
  auto encoded_suffix = encoding_callback(suffix);
  if (!encoded_suffix) return encoded_suffix.error();
  // Let part be a new part whose type is type, value is regexp value,
  // modifier is modifier, name is name, prefix is encoded prefix, and suffix
  // is encoded suffix.
  // Append part to parser's part list.
  parts.emplace_back(type, std::move(regexp_value), modifier, std::move(name),
                     std::move(*encoded_prefix), std::move(*encoded_suffix));
  return std::nullopt;
}

template <url_pattern_encoding_callback F>
tl::expected<std::vector<url_pattern_part>, errors> parse_pattern_string(
    std::string_view input, url_pattern_compile_component_options& options,
    F& encoding_callback) {
  ada_log("parse_pattern_string input=", input);
  // Let parser be a new pattern parser whose encoding callback is encoding
  // callback and segment wildcard regexp is the result of running generate a
  // segment wildcard regexp given options.
  auto parser = url_pattern_parser<F>(
      encoding_callback, generate_segment_wildcard_regexp(options));
  // Set parser's token list to the result of running tokenize given input and
  // "strict".
  auto tokenize_result = tokenize(input, token_policy::strict);
  if (!tokenize_result) {
    ada_log("parse_pattern_string tokenize failed");
    return tl::unexpected(tokenize_result.error());
  }
  parser.tokens = std::move(*tokenize_result);

  // While parser's index is less than parser's token list's size:
  while (parser.can_continue()) {
    // Let char token be the result of running try to consume a token given
    // parser and "char".
    auto char_token = parser.try_consume_token(token_type::CHAR);
    // Let name token be the result of running try to consume a token given
    // parser and "name".
    auto name_token = parser.try_consume_token(token_type::NAME);
    // Let regexp or wildcard token be the result of running try to consume a
    // regexp or wildcard token given parser and name token.
    auto regexp_or_wildcard_token =
        parser.try_consume_regexp_or_wildcard_token(name_token);
    // If name token is not null or regexp or wildcard token is not null:
    if (name_token || regexp_or_wildcard_token) {
      // Let prefix be the empty string.
      std::string prefix{};
      // If char token is not null then set prefix to char token's value.
      if (char_token) prefix = char_token->value;
      // If prefix is not the empty string and not options's prefix code point:
      if (!prefix.empty() && prefix != options.get_prefix()) {
        // Append prefix to the end of parser's pending fixed value.
        parser.pending_fixed_value.append(prefix);
        // Set prefix to the empty string.
        prefix.clear();
      }
      // Run maybe add a part from the pending fixed value given parser.
      if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) {
        ada_log("maybe_add_part_from_the_pending_fixed_value failed");
        return tl::unexpected(*error);
      }
      // Let modifier token be the result of running try to consume a modifier
      // token given parser.
      auto modifier_token = parser.try_consume_modifier_token();
      // Run add a part given parser, prefix, name token, regexp or wildcard
      // token, the empty string, and modifier token.
      if (auto error =
              parser.add_part(prefix, name_token, regexp_or_wildcard_token, "",
                              modifier_token)) {
        ada_log("parser.add_part failed");
        return tl::unexpected(*error);
      }
      // Continue.
      continue;
    }

    // Let fixed token be char token.
    auto fixed_token = char_token;
    // If fixed token is null, then set fixed token to the result of running try
    // to consume a token given parser and "escaped-char".
    if (!fixed_token)
      fixed_token = parser.try_consume_token(token_type::ESCAPED_CHAR);
    // If fixed token is not null:
    if (fixed_token) {
      // Append fixed token's value to parser's pending fixed value.
      parser.pending_fixed_value.append(fixed_token->value);
      // Continue.
      continue;
    }
    // Let open token be the result of running try to consume a token given
    // parser and "open".
    auto open_token = parser.try_consume_token(token_type::OPEN);
    // If open token is not null:
    if (open_token) {
      // Set prefix be the result of running consume text given parser.
      auto prefix_ = parser.consume_text();
      // Set name token to the result of running try to consume a token given
      // parser and "name".
      name_token = parser.try_consume_token(token_type::NAME);
      // Set regexp or wildcard token to the result of running try to consume a
      // regexp or wildcard token given parser and name token.
      regexp_or_wildcard_token =
          parser.try_consume_regexp_or_wildcard_token(name_token);
      // Let suffix be the result of running consume text given parser.
      auto suffix_ = parser.consume_text();
      // Run consume a required token given parser and "close".
      if (!parser.consume_required_token(token_type::CLOSE)) {
        ada_log("parser.consume_required_token failed");
        return tl::unexpected(errors::type_error);
      }
      // Set modifier token to the result of running try to consume a modifier
      // token given parser.
      auto modifier_token = parser.try_consume_modifier_token();
      // Run add a part given parser, prefix, name token, regexp or wildcard
      // token, suffix, and modifier token.
      if (auto error =
              parser.add_part(prefix_, name_token, regexp_or_wildcard_token,
                              suffix_, modifier_token)) {
        return tl::unexpected(*error);
      }
      // Continue.
      continue;
    }
    // Run maybe add a part from the pending fixed value given parser.
    if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) {
      ada_log("maybe_add_part_from_the_pending_fixed_value failed on line 992");
      return tl::unexpected(*error);
    }
    // Run consume a required token given parser and "end".
    if (!parser.consume_required_token(token_type::END)) {
      return tl::unexpected(errors::type_error);
    }
  }
  ada_log("parser.parts size is: ", parser.parts.size());
  // Return parser's part list.
  return parser.parts;
}

template <url_pattern_regex::regex_concept regex_provider>
bool protocol_component_matches_special_scheme(
    url_pattern_component<regex_provider>& component) {
  // Optimization: Use fast_test for simple patterns to avoid regex overhead
  switch (component.type) {
    case url_pattern_component_type::EMPTY:
      // Empty pattern can't match any special scheme
      return false;
    case url_pattern_component_type::EXACT_MATCH:
      // Direct string comparison for exact match patterns
      return component.exact_match_value == "http" ||
             component.exact_match_value == "https" ||
             component.exact_match_value == "ws" ||
             component.exact_match_value == "wss" ||
             component.exact_match_value == "ftp" ||
             component.exact_match_value == "file";
    case url_pattern_component_type::FULL_WILDCARD:
      // Full wildcard matches everything including special schemes
      return true;
    case url_pattern_component_type::REGEXP:
      // Fall back to regex matching for complex patterns
      auto& regex = component.regexp;
      return regex_provider::regex_match("http", regex) ||
             regex_provider::regex_match("https", regex) ||
             regex_provider::regex_match("ws", regex) ||
             regex_provider::regex_match("wss", regex) ||
             regex_provider::regex_match("ftp", regex) ||
             regex_provider::regex_match("file", regex);
  }
  ada::unreachable();
}

template <url_pattern_regex::regex_concept regex_provider>
inline std::optional<errors> constructor_string_parser<
    regex_provider>::compute_protocol_matches_special_scheme_flag() {
  ada_log(
      "constructor_string_parser::compute_protocol_matches_special_scheme_"
      "flag");
  // Let protocol string be the result of running make a component string given
  // parser.
  auto protocol_string = make_component_string();
  // Let protocol component be the result of compiling a component given
  // protocol string, canonicalize a protocol, and default options.
  auto protocol_component = url_pattern_component<regex_provider>::compile(
      protocol_string, canonicalize_protocol,
      url_pattern_compile_component_options::DEFAULT);
  if (!protocol_component) {
    ada_log("url_pattern_component::compile failed for protocol_string ",
            protocol_string);
    return protocol_component.error();
  }
  // If the result of running protocol component matches a special scheme given
  // protocol component is true, then set parser's protocol matches a special
  // scheme flag to true.
  if (protocol_component_matches_special_scheme(*protocol_component)) {
    protocol_matches_a_special_scheme_flag = true;
  }
  return std::nullopt;
}

template <url_pattern_regex::regex_concept regex_provider>
tl::expected<url_pattern_init, errors>
constructor_string_parser<regex_provider>::parse(std::string_view input) {
  ada_log("constructor_string_parser::parse input=", input);
  // Let parser be a new constructor string parser whose input is input and
  // token list is the result of running tokenize given input and "lenient".
  auto token_list = tokenize(input, token_policy::lenient);
  if (!token_list) {
    return tl::unexpected(token_list.error());
  }
  auto parser = constructor_string_parser(input, std::move(*token_list));

  // While parser's token index is less than parser's token list size:
  while (parser.token_index < parser.token_list.size()) {
    // Set parser's token increment to 1.
    parser.token_increment = 1;

    // If parser's token list[parser's token index]'s type is "end" then:
    if (parser.token_list[parser.token_index].type == token_type::END) {
      // If parser's state is "init":
      if (parser.state == State::INIT) {
        // Run rewind given parser.
        parser.rewind();
        // If the result of running is a hash prefix given parser is true, then
        // run change state given parser, "hash" and 1.
        if (parser.is_hash_prefix()) {
          parser.change_state(State::HASH, 1);
        } else if (parser.is_search_prefix()) {
          // Otherwise if the result of running is a search prefix given parser
          // is true: Run change state given parser, "search" and 1.
          parser.change_state(State::SEARCH, 1);
        } else {
          // Run change state given parser, "pathname" and 0.
          parser.change_state(State::PATHNAME, 0);
        }
        // Increment parser's token index by parser's token increment.
        parser.token_index += parser.token_increment;
        // Continue.
        continue;
      }

      if (parser.state == State::AUTHORITY) {
        // If parser's state is "authority":
        // Run rewind and set state given parser, and "hostname".
        parser.rewind();
        parser.change_state(State::HOSTNAME, 0);
        // Increment parser's token index by parser's token increment.
        parser.token_index += parser.token_increment;
        // Continue.
        continue;
      }

      // Run change state given parser, "done" and 0.
      parser.change_state(State::DONE, 0);
      // Break.
      break;
    }

    // If the result of running is a group open given parser is true:
    if (parser.is_group_open()) {
      // Increment parser's group depth by 1.
      parser.group_depth += 1;
      // Increment parser's token index by parser's token increment.
      parser.token_index += parser.token_increment;
      // Continue.
      continue;
    }

    // If parser's group depth is greater than 0:
    if (parser.group_depth > 0) {
      // If the result of running is a group close given parser is true, then
      // decrement parser's group depth by 1.
      if (parser.is_group_close()) {
        parser.group_depth -= 1;
      } else {
        // Increment parser's token index by parser's token increment.
        parser.token_index += parser.token_increment;
        continue;
      }
    }

    // Switch on parser's state and run the associated steps:
    switch (parser.state) {
      case State::INIT: {
        // If the result of running is a protocol suffix given parser is true:
        if (parser.is_protocol_suffix()) {
          // Run rewind and set state given parser and "protocol".
          parser.rewind();
          parser.change_state(State::PROTOCOL, 0);
        }
        break;
      }
      case State::PROTOCOL: {
        // If the result of running is a protocol suffix given parser is true:
        if (parser.is_protocol_suffix()) {
          // Run compute protocol matches a special scheme flag given parser.
          if (const auto error =
                  parser.compute_protocol_matches_special_scheme_flag()) {
            ada_log("compute_protocol_matches_special_scheme_flag failed");
            return tl::unexpected(*error);
          }
          // Let next state be "pathname".
          auto next_state = State::PATHNAME;
          // Let skip be 1.
          auto skip = 1;
          // If the result of running next is authority slashes given parser is
          // true:
          if (parser.next_is_authority_slashes()) {
            // Set next state to "authority".
            next_state = State::AUTHORITY;
            // Set skip to 3.
            skip = 3;
          } else if (parser.protocol_matches_a_special_scheme_flag) {
            // Otherwise if parser's protocol matches a special scheme flag is
            // true, then set next state to "authority".
            next_state = State::AUTHORITY;
          }

          // Run change state given parser, next state, and skip.
          parser.change_state(next_state, skip);
        }
        break;
      }
      case State::AUTHORITY: {
        // If the result of running is an identity terminator given parser is
        // true, then run rewind and set state given parser and "username".
        if (parser.is_an_identity_terminator()) {
          parser.rewind();
          parser.change_state(State::USERNAME, 0);
        } else if (parser.is_pathname_start() || parser.is_search_prefix() ||
                   parser.is_hash_prefix()) {
          // Otherwise if any of the following are true:
          // - the result of running is a pathname start given parser;
          // - the result of running is a search prefix given parser; or
          // - the result of running is a hash prefix given parser,
          // then run rewind and set state given parser and "hostname".
          parser.rewind();
          parser.change_state(State::HOSTNAME, 0);
        }
        break;
      }
      case State::USERNAME: {
        // If the result of running is a password prefix given parser is true,
        // then run change state given parser, "password", and 1.
        if (parser.is_password_prefix()) {
          parser.change_state(State::PASSWORD, 1);
        } else if (parser.is_an_identity_terminator()) {
          // Otherwise if the result of running is an identity terminator given
          // parser is true, then run change state given parser, "hostname",
          // and 1.
          parser.change_state(State::HOSTNAME, 1);
        }
        break;
      }
      case State::PASSWORD: {
        // If the result of running is an identity terminator given parser is
        // true, then run change state given parser, "hostname", and 1.
        if (parser.is_an_identity_terminator()) {
          parser.change_state(State::HOSTNAME, 1);
        }
        break;
      }
      case State::HOSTNAME: {
        // If the result of running is an IPv6 open given parser is true, then
        // increment parser's hostname IPv6 bracket depth by 1.
        if (parser.is_an_ipv6_open()) {
          parser.hostname_ipv6_bracket_depth += 1;
        } else if (parser.is_an_ipv6_close()) {
          // Otherwise if the result of running is an IPv6 close given parser is
          // true, then decrement parser's hostname IPv6 bracket depth by 1.
          parser.hostname_ipv6_bracket_depth -= 1;
        } else if (parser.is_port_prefix() &&
                   parser.hostname_ipv6_bracket_depth == 0) {
          // Otherwise if the result of running is a port prefix given parser is
          // true and parser's hostname IPv6 bracket depth is zero, then run
          // change state given parser, "port", and 1.
          parser.change_state(State::PORT, 1);
        } else if (parser.is_pathname_start()) {
          // Otherwise if the result of running is a pathname start given parser
          // is true, then run change state given parser, "pathname", and 0.
          parser.change_state(State::PATHNAME, 0);
        } else if (parser.is_search_prefix()) {
          // Otherwise if the result of running is a search prefix given parser
          // is true, then run change state given parser, "search", and 1.
          parser.change_state(State::SEARCH, 1);
        } else if (parser.is_hash_prefix()) {
          // Otherwise if the result of running is a hash prefix given parser is
          // true, then run change state given parser, "hash", and 1.
          parser.change_state(State::HASH, 1);
        }

        break;
      }
      case State::PORT: {
        // If the result of running is a pathname start given parser is true,
        // then run change state given parser, "pathname", and 0.
        if (parser.is_pathname_start()) {
          parser.change_state(State::PATHNAME, 0);
        } else if (parser.is_search_prefix()) {
          // Otherwise if the result of running is a search prefix given parser
          // is true, then run change state given parser, "search", and 1.
          parser.change_state(State::SEARCH, 1);
        } else if (parser.is_hash_prefix()) {
          // Otherwise if the result of running is a hash prefix given parser is
          // true, then run change state given parser, "hash", and 1.
          parser.change_state(State::HASH, 1);
        }
        break;
      }
      case State::PATHNAME: {
        // If the result of running is a search prefix given parser is true,
        // then run change state given parser, "search", and 1.
        if (parser.is_search_prefix()) {
          parser.change_state(State::SEARCH, 1);
        } else if (parser.is_hash_prefix()) {
          // Otherwise if the result of running is a hash prefix given parser is
          // true, then run change state given parser, "hash", and 1.
          parser.change_state(State::HASH, 1);
        }
        break;
      }
      case State::SEARCH: {
        // If the result of running is a hash prefix given parser is true, then
        // run change state given parser, "hash", and 1.
        if (parser.is_hash_prefix()) {
          parser.change_state(State::HASH, 1);
        }
        break;
      }
      case State::HASH: {
        // Do nothing
        break;
      }
      default: {
        // Assert: This step is never reached.
        unreachable();
      }
    }

    // Increment parser's token index by parser's token increment.
    parser.token_index += parser.token_increment;
  }

  // If parser's result contains "hostname" and not "port", then set parser's
  // result["port"] to the empty string.
  if (parser.result.hostname && !parser.result.port) {
    parser.result.port = "";
  }

  // Return parser's result.
  return parser.result;
}

}  // namespace ada::url_pattern_helpers
#endif  // ADA_INCLUDE_URL_PATTERN
#endif
/* end file include/ada/url_pattern_helpers-inl.h */

// Public API
/* begin file include/ada/ada_version.h */
/**
 * @file ada_version.h
 * @brief Definitions for Ada's version number.
 */
#ifndef ADA_ADA_VERSION_H
#define ADA_ADA_VERSION_H

#define ADA_VERSION "4.0.0"

namespace ada {

enum {
  ADA_VERSION_MAJOR = 4,
  ADA_VERSION_MINOR = 0,
  ADA_VERSION_REVISION = 0,
};

}  // namespace ada

#endif  // ADA_ADA_VERSION_H
/* end file include/ada/ada_version.h */
/* begin file include/ada/implementation-inl.h */
/**
 * @file implementation-inl.h
 */
#ifndef ADA_IMPLEMENTATION_INL_H
#define ADA_IMPLEMENTATION_INL_H



#include <variant>
#include <string_view>

namespace ada {

#if ADA_INCLUDE_URL_PATTERN
template <url_pattern_regex::regex_concept regex_provider>
ada_warn_unused tl::expected<url_pattern<regex_provider>, errors>
parse_url_pattern(std::variant<std::string_view, url_pattern_init>&& input,
                  const std::string_view* base_url,
                  const url_pattern_options* options) {
  return parser::parse_url_pattern_impl<regex_provider>(std::move(input),
                                                        base_url, options);
}
#endif  // ADA_INCLUDE_URL_PATTERN

}  // namespace ada

#endif  // ADA_IMPLEMENTATION_INL_H
/* end file include/ada/implementation-inl.h */

#endif  // ADA_H
/* end file include/ada.h */