//# This file is a part of toml++ and is subject to the the terms of the MIT license.
//# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
//# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
// SPDX-License-Identifier: MIT
#pragma once

#include "forward_declarations.hpp"
#include "std_map.hpp"
#include "std_initializer_list.hpp"
#include "array.hpp"
#include "make_node.hpp"
#include "node_view.hpp"
#include "key.hpp"
#include "header_start.hpp"

/// \cond
TOML_IMPL_NAMESPACE_START
{
	template <bool IsConst>
	struct table_proxy_pair
	{
		using value_type = std::conditional_t<IsConst, const node, node>;

		const toml::key& first;
		value_type& second;
	};

	template <bool IsConst>
	class table_iterator
	{
	  private:
		template <bool>
		friend class table_iterator;

		using proxy_type		   = table_proxy_pair<IsConst>;
		using mutable_map_iterator = std::map<toml::key, node_ptr, std::less<>>::iterator;
		using const_map_iterator   = std::map<toml::key, node_ptr, std::less<>>::const_iterator;
		using map_iterator		   = std::conditional_t<IsConst, const_map_iterator, mutable_map_iterator>;

		mutable map_iterator iter_;
		alignas(proxy_type) mutable unsigned char proxy_[sizeof(proxy_type)];
		mutable bool proxy_instantiated_ = false;

		TOML_NODISCARD
		proxy_type* get_proxy() const noexcept
		{
			if (!proxy_instantiated_)
			{
				auto p = ::new (static_cast<void*>(proxy_)) proxy_type{ iter_->first, *iter_->second.get() };
				proxy_instantiated_ = true;
				return p;
			}
			else
				return TOML_LAUNDER(reinterpret_cast<proxy_type*>(proxy_));
		}

	  public:
		TOML_NODISCARD_CTOR
		table_iterator() noexcept = default;

		TOML_NODISCARD_CTOR
		explicit table_iterator(mutable_map_iterator iter) noexcept //
			: iter_{ iter }
		{}

		TOML_CONSTRAINED_TEMPLATE(C, bool C = IsConst)
		TOML_NODISCARD_CTOR
		explicit table_iterator(const_map_iterator iter) noexcept //
			: iter_{ iter }
		{}

		TOML_CONSTRAINED_TEMPLATE(C, bool C = IsConst)
		TOML_NODISCARD_CTOR
		table_iterator(const table_iterator<false>& other) noexcept //
			: iter_{ other.iter_ }
		{}

		TOML_NODISCARD_CTOR
		table_iterator(const table_iterator& other) noexcept //
			: iter_{ other.iter_ }
		{}

		table_iterator& operator=(const table_iterator& rhs) noexcept
		{
			iter_				= rhs.iter_;
			proxy_instantiated_ = false;
			return *this;
		}

		using value_type		= table_proxy_pair<IsConst>;
		using reference			= value_type&;
		using pointer			= value_type*;
		using difference_type	= typename std::iterator_traits<map_iterator>::difference_type;
		using iterator_category = typename std::iterator_traits<map_iterator>::iterator_category;

		table_iterator& operator++() noexcept // ++pre
		{
			++iter_;
			proxy_instantiated_ = false;
			return *this;
		}

		table_iterator operator++(int) noexcept // post++
		{
			table_iterator out{ iter_ };
			++iter_;
			proxy_instantiated_ = false;
			return out;
		}

		table_iterator& operator--() noexcept // --pre
		{
			--iter_;
			proxy_instantiated_ = false;
			return *this;
		}

		table_iterator operator--(int) noexcept // post--
		{
			table_iterator out{ iter_ };
			--iter_;
			proxy_instantiated_ = false;
			return out;
		}

		TOML_PURE_INLINE_GETTER
		reference operator*() const noexcept
		{
			return *get_proxy();
		}

		TOML_PURE_INLINE_GETTER
		pointer operator->() const noexcept
		{
			return get_proxy();
		}

		TOML_PURE_INLINE_GETTER
		explicit operator const map_iterator&() const noexcept
		{
			return iter_;
		}

		TOML_CONSTRAINED_TEMPLATE(!C, bool C = IsConst)
		TOML_PURE_INLINE_GETTER
		explicit operator const const_map_iterator() const noexcept
		{
			return iter_;
		}

		TOML_PURE_INLINE_GETTER
		friend bool operator==(const table_iterator& lhs, const table_iterator& rhs) noexcept
		{
			return lhs.iter_ == rhs.iter_;
		}

		TOML_PURE_INLINE_GETTER
		friend bool operator!=(const table_iterator& lhs, const table_iterator& rhs) noexcept
		{
			return lhs.iter_ != rhs.iter_;
		}
	};

	struct table_init_pair
	{
		mutable toml::key key;
		mutable node_ptr value;

		template <typename K, typename V>
		TOML_NODISCARD_CTOR
		table_init_pair(K&& k, V&& v, value_flags flags = preserve_source_value_flags) //
			: key{ static_cast<K&&>(k) },
			  value{ make_node(static_cast<V&&>(v), flags) }
		{}
	};
}
TOML_IMPL_NAMESPACE_END;
/// \endcond

TOML_NAMESPACE_START
{
	/// \brief A BidirectionalIterator for iterating over key-value pairs in a toml::table.
	using table_iterator = POXY_IMPLEMENTATION_DETAIL(impl::table_iterator<false>);

	/// \brief A BidirectionalIterator for iterating over const key-value pairs in a toml::table.
	using const_table_iterator = POXY_IMPLEMENTATION_DETAIL(impl::table_iterator<true>);

	/// \brief	A TOML table.
	///
	/// \detail The interface of this type is modeled after std::map, with some
	/// 		additional considerations made for the heterogeneous nature of a
	/// 		TOML table.
	///
	/// \cpp
	/// toml::table tbl = toml::parse(R"(
	///
	/// 	[animals]
	/// 	cats = [ "tiger", "lion", "puma" ]
	/// 	birds = [ "macaw", "pigeon", "canary" ]
	/// 	fish = [ "salmon", "trout", "carp" ]
	///
	/// )"sv);
	///
	/// // operator[] retrieves node-views
	/// std::cout << "cats: " << tbl["animals"]["cats"] << "\n";
	/// std::cout << "fish[1]: " << tbl["animals"]["fish"][1] << "\n";
	///
	/// // at_path() does fully-qualified "toml path" lookups
	/// std::cout << "cats: " << tbl.at_path("animals.cats") << "\n";
	/// std::cout << "fish[1]: " << tbl.at_path("animals.fish[1]") << "\n";
	/// \ecpp
	///
	/// \out
	/// cats: ['tiger', 'lion', 'puma']
	/// fish[1] : 'trout'
	/// cats : ['tiger', 'lion', 'puma']
	/// fish[1] : 'trout'
	/// \eout
	class TOML_EXPORTED_CLASS table : public node
	{
	  private:
		/// \cond

		using map_type			 = std::map<toml::key, impl::node_ptr, std::less<>>;
		using map_pair			 = std::pair<const toml::key, impl::node_ptr>;
		using map_iterator		 = typename map_type::iterator;
		using const_map_iterator = typename map_type::const_iterator;
		map_type map_;

		bool inline_ = false;

		TOML_NODISCARD_CTOR
		TOML_EXPORTED_MEMBER_FUNCTION
		table(const impl::table_init_pair*, const impl::table_init_pair*);

		/// \endcond

	  public:
		/// \brief	Default constructor.
		TOML_NODISCARD_CTOR
		TOML_EXPORTED_MEMBER_FUNCTION
		table() noexcept;

		TOML_EXPORTED_MEMBER_FUNCTION
		~table() noexcept;

		/// \brief	Copy constructor.
		TOML_NODISCARD_CTOR
		TOML_EXPORTED_MEMBER_FUNCTION
		table(const table&);

		/// \brief	Move constructor.
		TOML_NODISCARD_CTOR
		TOML_EXPORTED_MEMBER_FUNCTION
		table(table&& other) noexcept;

		/// \brief	Constructs a table with one or more initial key-value pairs.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "foo", 1 },
		///		{ "bar", 2.0 },
		///		{ "kek", "three" }
		///	};
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { foo = 1, bar = 2.0, kek = "three" }
		/// \eout
		///
		/// \param 	kvps	A list of key-value pairs used to initialize the table.
		TOML_NODISCARD_CTOR
		TOML_EXPORTED_MEMBER_FUNCTION
		explicit table(std::initializer_list<impl::table_init_pair> kvps) //
			: table(kvps.begin(), kvps.end())
		{}

		/// \brief	Copy-assignment operator.
		TOML_EXPORTED_MEMBER_FUNCTION
		table& operator=(const table&);

		/// \brief	Move-assignment operator.
		TOML_EXPORTED_MEMBER_FUNCTION
		table& operator=(table&& rhs) noexcept;

		/// \name Type checks
		/// @{

		/// \brief Returns #toml::node_type::table.
		TOML_CONST_INLINE_GETTER
		node_type type() const noexcept final
		{
			return node_type::table;
		}

		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		bool is_homogeneous(node_type ntype) const noexcept final;

		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		bool is_homogeneous(node_type ntype, node*& first_nonmatch) noexcept final;

		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		bool is_homogeneous(node_type ntype, const node*& first_nonmatch) const noexcept final;

		/// \cond
		template <typename ElemType = void>
		TOML_PURE_GETTER
		bool is_homogeneous() const noexcept
		{
			using type = impl::remove_cvref<impl::unwrap_node<ElemType>>;
			static_assert(std::is_void_v<type> || toml::is_value<type> || toml::is_container<type>,
						  "The template type argument of table::is_homogeneous() must be void or one "
						  "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST);

			return is_homogeneous(impl::node_type_of<type>);
		}
		/// \endcond

		/// \brief Returns `true`.
		TOML_CONST_INLINE_GETTER
		bool is_table() const noexcept final
		{
			return true;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_array() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_PURE_GETTER
		bool is_array_of_tables() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_value() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_string() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_integer() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_floating_point() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_number() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_boolean() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_date() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_time() const noexcept final
		{
			return false;
		}

		/// \brief Returns `false`.
		TOML_CONST_INLINE_GETTER
		bool is_date_time() const noexcept final
		{
			return false;
		}

		/// @}

		/// \name Type casts
		/// @{

		/// \brief Returns a pointer to the table.
		TOML_CONST_INLINE_GETTER
		table* as_table() noexcept final
		{
			return this;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		array* as_array() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<std::string>* as_string() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<int64_t>* as_integer() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<double>* as_floating_point() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<bool>* as_boolean() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<date>* as_date() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<time>* as_time() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		toml::value<date_time>* as_date_time() noexcept final
		{
			return nullptr;
		}

		/// \brief Returns a const-qualified pointer to the table.
		TOML_CONST_INLINE_GETTER
		const table* as_table() const noexcept final
		{
			return this;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const array* as_array() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<std::string>* as_string() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<int64_t>* as_integer() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<double>* as_floating_point() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<bool>* as_boolean() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<date>* as_date() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<time>* as_time() const noexcept final
		{
			return nullptr;
		}

		/// \brief Returns `nullptr`.
		TOML_CONST_INLINE_GETTER
		const toml::value<date_time>* as_date_time() const noexcept final
		{
			return nullptr;
		}

		/// @}

		/// \name Metadata
		/// @{

		/// \brief	Returns true if this table is an inline table.
		///
		/// \remarks Runtime-constructed tables (i.e. those not created during
		/// 		 parsing) are not inline by default.
		TOML_PURE_INLINE_GETTER
		bool is_inline() const noexcept
		{
			return inline_;
		}

		/// \brief	Sets whether this table is a TOML inline table.
		///
		/// \detail \godbolt{an9xdj}
		///
		/// \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 },
		///		{ "d", toml::table{ { "e", 4 } } }
		///	};
		/// std::cout << "is inline? "sv << tbl.is_inline() << "\n";
		/// std::cout << tbl << "\n\n";
		///
		/// tbl.is_inline(!tbl.is_inline());
		/// std::cout << "is inline? "sv << tbl.is_inline() << "\n";
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// is inline? false
		/// a = 1
		/// b = 2
		/// c = 3
		///
		/// [d]
		/// e = 4
		///
		///
		/// is inline? true
		/// { a = 1, b = 2, c = 3, d = { e = 4 } }
		/// \eout
		///
		/// \remarks A table being 'inline' is only relevent during printing;
		/// 		 it has no effect on the general functionality of the table
		/// 		 object.
		///
		/// \param 	val	The new value for 'inline'.
		void is_inline(bool val) noexcept
		{
			inline_ = val;
		}

		/// @}

		/// \name Value retrieval
		/// @{

		/// \brief	Gets the node at a specific key.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 42, },
		///		{ "b", "is the meaning of life, apparently." }
		///	};
		///	std::cout << R"(node ["a"] exists: )"sv << !!arr.get("a") << "\n";
		///	std::cout << R"(node ["b"] exists: )"sv << !!arr.get("b") << "\n";
		///	std::cout << R"(node ["c"] exists: )"sv << !!arr.get("c") << "\n";
		/// if (auto val = arr.get("a"))
		///		std::cout << R"(node ["a"] was an )"sv << val->type() << "\n";
		/// \ecpp
		///
		/// \out
		/// node ["a"] exists: true
		/// node ["b"] exists: true
		/// node ["c"] exists: false
		/// node ["a"] was an integer
		/// \eout
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A pointer to the node at the specified key, or nullptr.
		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		node* get(std::string_view key) noexcept;

		/// \brief	Gets the node at a specific key (const overload).
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A pointer to the node at the specified key, or nullptr.
		TOML_PURE_INLINE_GETTER
		const node* get(std::string_view key) const noexcept
		{
			return const_cast<table&>(*this).get(key);
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets the node at a specific key.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A pointer to the node at the specified key, or nullptr.
		TOML_NODISCARD
		node* get(std::wstring_view key)
		{
			if (empty())
				return nullptr;

			return get(impl::narrow(key));
		}

		/// \brief	Gets the node at a specific key (const overload).
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A pointer to the node at the specified key, or nullptr.
		TOML_NODISCARD
		const node* get(std::wstring_view key) const
		{
			return const_cast<table&>(*this).get(key);
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets the node at a specific key if it is a particular type.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 42, },
		///		{ "b", "is the meaning of life, apparently." }
		///	};
		/// if (auto val = arr.get_as<int64_t>("a"))
		///		std::cout << R"(node ["a"] was an integer with value )"sv << **val << "\n";
		/// \ecpp
		///
		/// \out
		/// node ["a"] was an integer with value 42
		/// \eout
		///
		/// \tparam	T		One of the TOML node or value types.
		/// \param 	key		The node's key.
		///
		/// \returns	A pointer to the node at the specified key if it was of the given type, or nullptr.
		template <typename T>
		TOML_PURE_GETTER
		impl::wrap_node<T>* get_as(std::string_view key) noexcept
		{
			const auto n = this->get(key);
			return n ? n->template as<T>() : nullptr;
		}

		/// \brief	Gets the node at a specific key if it is a particular type (const overload).
		///
		/// \tparam	T		One of the TOML node or value types.
		/// \param 	key		The node's key.
		///
		/// \returns	A pointer to the node at the specified key if it was of the given type, or nullptr.
		template <typename T>
		TOML_PURE_GETTER
		const impl::wrap_node<T>* get_as(std::string_view key) const noexcept
		{
			return const_cast<table&>(*this).template get_as<T>(key);
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets the node at a specific key if it is a particular type.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \tparam	T		One of the TOML node or value types.
		/// \param 	key		The node's key.
		///
		/// \returns	A pointer to the node at the specified key if it was of the given type, or nullptr.
		template <typename T>
		TOML_NODISCARD
		impl::wrap_node<T>* get_as(std::wstring_view key)
		{
			if (empty())
				return nullptr;

			return get_as<T>(impl::narrow(key));
		}

		/// \brief	Gets the node at a specific key if it is a particular type (const overload).
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \tparam	T		One of the TOML node or value types.
		/// \param 	key		The node's key.
		///
		/// \returns	A pointer to the node at the specified key if it was of the given type, or nullptr.
		template <typename T>
		TOML_NODISCARD
		const impl::wrap_node<T>* get_as(std::wstring_view key) const
		{
			return const_cast<table&>(*this).template get_as<T>(key);
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets a reference to the element at a specific key, throwing `std::out_of_range` if none existed.
		TOML_NODISCARD
		TOML_EXPORTED_MEMBER_FUNCTION
		node& at(std::string_view key);

		/// \brief	Gets a reference to the element at a specific key, throwing `std::out_of_range` if none existed.
		TOML_NODISCARD
		const node& at(std::string_view key) const
		{
			return const_cast<table&>(*this).at(key);
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets a reference to the element at a specific key, throwing `std::out_of_range` if none existed.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		TOML_NODISCARD
		node& at(std::wstring_view key)
		{
			return at(impl::narrow(key));
		}

		/// \brief	Gets a reference to the element at a specific key, throwing `std::out_of_range` if none existed.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		TOML_NODISCARD
		const node& at(std::wstring_view key) const
		{
			return const_cast<table&>(*this).at(key);
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// @}

		/// \name Iteration
		/// @{

		/// \brief A BidirectionalIterator for iterating over key-value pairs in a toml::table.
		using iterator = toml::table_iterator;

		/// \brief A BidirectionalIterator for iterating over const key-value pairs in a toml::table.
		using const_iterator = toml::const_table_iterator;

		/// \brief	Returns an iterator to the first key-value pair.
		TOML_PURE_INLINE_GETTER
		iterator begin() noexcept
		{
			return iterator{ map_.begin() };
		}

		/// \brief	Returns an iterator to the first key-value pair.
		TOML_PURE_INLINE_GETTER
		const_iterator begin() const noexcept
		{
			return const_iterator{ map_.cbegin() };
		}

		/// \brief	Returns an iterator to the first key-value pair.
		TOML_PURE_INLINE_GETTER
		const_iterator cbegin() const noexcept
		{
			return const_iterator{ map_.cbegin() };
		}

		/// \brief	Returns an iterator to one-past-the-last key-value pair.
		TOML_PURE_INLINE_GETTER
		iterator end() noexcept
		{
			return iterator{ map_.end() };
		}

		/// \brief	Returns an iterator to one-past-the-last key-value pair.
		TOML_PURE_INLINE_GETTER
		const_iterator end() const noexcept
		{
			return const_iterator{ map_.cend() };
		}

		/// \brief	Returns an iterator to one-past-the-last key-value pair.
		TOML_PURE_INLINE_GETTER
		const_iterator cend() const noexcept
		{
			return const_iterator{ map_.cend() };
		}

	  private:
		/// \cond

		template <typename T, typename Table>
		using for_each_value_ref = impl::copy_cvref<impl::wrap_node<impl::remove_cvref<impl::unwrap_node<T>>>, Table>;

		template <typename Func, typename Table, typename T>
		using can_for_each = std::disjunction<std::is_invocable<Func, const key&, for_each_value_ref<T, Table>>, //
											  std::is_invocable<Func, for_each_value_ref<T, Table>>>;

		template <typename Func, typename Table, typename T>
		using can_for_each_nothrow = std::conditional_t<
			// first form
			std::is_invocable_v<Func, const key&, for_each_value_ref<T, Table>>,
			std::is_nothrow_invocable<Func, const key&, for_each_value_ref<T, Table>>,
			std::conditional_t<
				// second form
				std::is_invocable_v<Func, for_each_value_ref<T, Table>>,
				std::is_nothrow_invocable<Func, for_each_value_ref<T, Table>>,
				std::false_type>>;

		template <typename Func, typename Table>
		using can_for_each_any = std::disjunction<can_for_each<Func, Table, table>,
												  can_for_each<Func, Table, array>,
												  can_for_each<Func, Table, std::string>,
												  can_for_each<Func, Table, int64_t>,
												  can_for_each<Func, Table, double>,
												  can_for_each<Func, Table, bool>,
												  can_for_each<Func, Table, date>,
												  can_for_each<Func, Table, time>,
												  can_for_each<Func, Table, date_time>>;

		template <typename Func, typename Table, typename T>
		using for_each_is_nothrow_one = std::disjunction<std::negation<can_for_each<Func, Table, T>>, //
														 can_for_each_nothrow<Func, Table, T>>;

		template <typename Func, typename Table>
		using for_each_is_nothrow = std::conjunction<for_each_is_nothrow_one<Func, Table, table>,
													 for_each_is_nothrow_one<Func, Table, array>,
													 for_each_is_nothrow_one<Func, Table, std::string>,
													 for_each_is_nothrow_one<Func, Table, int64_t>,
													 for_each_is_nothrow_one<Func, Table, double>,
													 for_each_is_nothrow_one<Func, Table, bool>,
													 for_each_is_nothrow_one<Func, Table, date>,
													 for_each_is_nothrow_one<Func, Table, time>,
													 for_each_is_nothrow_one<Func, Table, date_time>>;

		template <typename Func, typename Table>
		static void do_for_each(Func&& visitor, Table&& tbl) //
			noexcept(for_each_is_nothrow<Func&&, Table&&>::value)
		{
			static_assert(can_for_each_any<Func&&, Table&&>::value,
						  "TOML table for_each visitors must be invocable for at least one of the toml::node "
						  "specializations:" TOML_SA_NODE_TYPE_LIST);

			using kvp_type = impl::copy_cv<map_pair, std::remove_reference_t<Table>>;

			for (kvp_type& kvp : tbl.map_)
			{
				using node_ref = impl::copy_cvref<toml::node, Table&&>;
				static_assert(std::is_reference_v<node_ref>);

#if TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN

#ifndef TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN_ACKNOWLEDGED
				static_assert(impl::always_false<Func, Table, kvp_type, node_ref>, //
							  TOML_RETURN_BOOL_FROM_FOR_EACH_BROKEN_MESSAGE);
#endif

				static_cast<node_ref>(*kvp.second)
					.visit(
						[&]([[maybe_unused]] auto&& v) //
						noexcept(for_each_is_nothrow_one<Func&&, Table&&, decltype(v)>::value)
						{
							using value_ref = for_each_value_ref<decltype(v), Table&&>;
							static_assert(std::is_reference_v<value_ref>);

							// func(key, val)
							if constexpr (std::is_invocable_v<Func&&, const key&, value_ref>)
							{
								static_cast<Func&&>(visitor)(static_cast<const key&>(kvp.first),
															 static_cast<value_ref>(v));
							}

							// func(val)
							else if constexpr (std::is_invocable_v<Func&&, value_ref>)
							{
								static_cast<Func&&>(visitor)(static_cast<value_ref>(v));
							}
						});

#else
				const auto keep_going =
					static_cast<node_ref>(*kvp.second)
						.visit(
							[&]([[maybe_unused]] auto&& v) //
							noexcept(for_each_is_nothrow_one<Func&&, Table&&, decltype(v)>::value)
							{
								using value_ref = for_each_value_ref<decltype(v), Table&&>;
								static_assert(std::is_reference_v<value_ref>);

								// func(key, val)
								if constexpr (std::is_invocable_v<Func&&, const key&, value_ref>)
								{
									using return_type = decltype(static_cast<Func&&>(
										visitor)(static_cast<const key&>(kvp.first), static_cast<value_ref>(v)));

									if constexpr (impl::is_constructible_or_convertible<bool, return_type>)
									{
										return static_cast<bool>(static_cast<Func&&>(
											visitor)(static_cast<const key&>(kvp.first), static_cast<value_ref>(v)));
									}
									else
									{
										static_cast<Func&&>(visitor)(static_cast<const key&>(kvp.first),
																	 static_cast<value_ref>(v));
										return true;
									}
								}

								// func(val)
								else if constexpr (std::is_invocable_v<Func&&, value_ref>)
								{
									using return_type =
										decltype(static_cast<Func&&>(visitor)(static_cast<value_ref>(v)));

									if constexpr (impl::is_constructible_or_convertible<bool, return_type>)
									{
										return static_cast<bool>(
											static_cast<Func&&>(visitor)(static_cast<value_ref>(v)));
									}
									else
									{
										static_cast<Func&&>(visitor)(static_cast<value_ref>(v));
										return true;
									}
								}

								// visitor not compatible with this particular type
								else
									return true;
							});

				if (!keep_going)
					return;
#endif
			}
		}

		/// \endcond

	  public:
		/// \brief	Invokes a visitor on each key-value pair in the table.
		///
		/// \tparam	Func	A callable type invocable with one of the following signatures:
		///					<ul>
		///					<li> `func(key, val)`
		///					<li> `func(val)`
		///					</ul>
		///					Where:
		///					<ul>
		///					<li> `key` will recieve a const reference to a toml::key
		///					<li> `val` will recieve the value as it's concrete type with cvref-qualifications matching the table
		///					</ul>
		///					Visitors returning `bool` (or something convertible to `bool`) will cause iteration to
		///					stop if they return `false`.
		///
		/// \param 	visitor	The visitor object.
		///
		/// \returns A reference to the table.
		///
		/// \details \cpp
		/// toml::table tbl{
		///		{ "0",  0      },
		/// 	{ "1",  1      },
		/// 	{ "2",  2      },
		/// 	{ "3",  3.0    },
		/// 	{ "4",  "four" },
		/// 	{ "5",  "five" },
		/// 	{ "6",  6      }
		/// };
		///
		/// // select only the integers using a strongly-typed visitor
		/// tbl.for_each([](toml::value<int64_t>& val)
		/// {
		/// 	std::cout << val << ", ";
		/// });
		/// std::cout << "\n";
		///
		/// // select all the numeric values using a generic visitor + is_number<> metafunction
		/// tbl.for_each([](auto&& val)
		/// {
		/// 	if constexpr (toml::is_number<decltype(val)>)
		/// 		std::cout << val << ", ";
		/// });
		/// std::cout << "\n";
		///
		/// // select all the numeric values until we encounter something non-numeric
		/// tbl.for_each([](auto&& val)
		/// {
		///		if constexpr (toml::is_number<decltype(val)>)
		///		{
		///			std::cout << val << ", ";
		///			return true; // "keep going"
		///		}
		///		else
		///			return false; // "stop!"
		///
		/// });
		/// std::cout << "\n\n";
		///
		/// // visitors may also recieve the key
		/// tbl.for_each([](const toml::key& key, auto&& val)
		/// {
		///		std::cout << key << ": " << val << "\n";
		/// });
		///
		/// \ecpp
		/// \out
		/// 0, 1, 2, 6,
		/// 0, 1, 2, 3.0, 6,
		/// 0, 1, 2, 3.0,
		///
		/// 0: 0
		/// 1: 1
		/// 2: 2
		/// 3: 3.0
		/// 4: 'four'
		/// 5: 'five'
		/// 6: 6
		/// \eout
		///
		/// \see node::visit()
		template <typename Func>
		table& for_each(Func&& visitor) & //
			noexcept(for_each_is_nothrow<Func&&, table&>::value)
		{
			do_for_each(static_cast<Func&&>(visitor), *this);
			return *this;
		}

		/// \brief	Invokes a visitor on each key-value pair in the table (rvalue overload).
		template <typename Func>
		table&& for_each(Func&& visitor) && //
			noexcept(for_each_is_nothrow<Func&&, table&&>::value)
		{
			do_for_each(static_cast<Func&&>(visitor), static_cast<table&&>(*this));
			return static_cast<table&&>(*this);
		}

		/// \brief	Invokes a visitor on each key-value pair in the table (const lvalue overload).
		template <typename Func>
		const table& for_each(Func&& visitor) const& //
			noexcept(for_each_is_nothrow<Func&&, const table&>::value)
		{
			do_for_each(static_cast<Func&&>(visitor), *this);
			return *this;
		}

		/// \brief	Invokes a visitor on each key-value pair in the table (const rvalue overload).
		template <typename Func>
		const table&& for_each(Func&& visitor) const&& //
			noexcept(for_each_is_nothrow<Func&&, const table&&>::value)
		{
			do_for_each(static_cast<Func&&>(visitor), static_cast<const table&&>(*this));
			return static_cast<const table&&>(*this);
		}

		/// @}

		/// \name Size and Capacity
		/// @{

		/// \brief	Returns true if the table is empty.
		TOML_PURE_INLINE_GETTER
		bool empty() const noexcept
		{
			return map_.empty();
		}

		/// \brief	Returns the number of key-value pairs in the table.
		TOML_PURE_INLINE_GETTER
		size_t size() const noexcept
		{
			return map_.size();
		}

		/// @}

		/// \name Searching
		/// @{

	  private:
		/// \cond

		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		map_iterator get_lower_bound(std::string_view) noexcept;

		/// \endcond

	  public:
		/// \brief Returns an iterator to the first key-value pair with key that is _not less_ than the given key.
		///
		/// \returns	An iterator to the first matching key-value pair, or #end().
		TOML_PURE_GETTER
		iterator lower_bound(std::string_view key) noexcept
		{
			return iterator{ get_lower_bound(key) };
		}

		/// \brief Returns a const iterator to the first key-value pair with key that is _not less_ than the given key.
		///
		/// \returns	An iterator to the first matching key-value pair, or #end().
		TOML_PURE_GETTER
		const_iterator lower_bound(std::string_view key) const noexcept
		{
			return const_iterator{ const_cast<table&>(*this).get_lower_bound(key) };
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief Returns an iterator to the first key-value pair with key that is _not less_ than the given key.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \returns	An iterator to the first matching key-value pair, or #end().
		TOML_NODISCARD
		iterator lower_bound(std::wstring_view key)
		{
			if (empty())
				return end();

			return lower_bound(impl::narrow(key));
		}

		/// \brief Returns a const iterator to the first key-value pair with key that is _not less_ than the given key.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \returns	An iterator to the first matching key-value pair, or #end().
		TOML_NODISCARD
		const_iterator lower_bound(std::wstring_view key) const
		{
			if (empty())
				return end();

			return lower_bound(impl::narrow(key));
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets an iterator to the node at a specific key.
		///
		/// \param 	key	The node's key.
		///
		/// \returns	An iterator to the node at the specified key, or end().
		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		iterator find(std::string_view key) noexcept;

		/// \brief	Gets an iterator to the node at a specific key (const overload)
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A const iterator to the node at the specified key, or cend().
		TOML_PURE_GETTER
		TOML_EXPORTED_MEMBER_FUNCTION
		const_iterator find(std::string_view key) const noexcept;

		/// \brief	Returns true if the table contains a node at the given key.
		TOML_PURE_GETTER
		bool contains(std::string_view key) const noexcept
		{
			return get(key) != nullptr;
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets an iterator to the node at a specific key.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key	The node's key.
		///
		/// \returns	An iterator to the node at the specified key, or end().
		TOML_NODISCARD
		iterator find(std::wstring_view key)
		{
			if (empty())
				return end();

			return find(impl::narrow(key));
		}

		/// \brief	Gets an iterator to the node at a specific key (const overload).
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key	The node's key.
		///
		/// \returns	A const iterator to the node at the specified key, or cend().
		TOML_NODISCARD
		const_iterator find(std::wstring_view key) const
		{
			return find(impl::narrow(key));
		}

		/// \brief	Returns true if the table contains a node at the given key.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		TOML_NODISCARD
		bool contains(std::wstring_view key) const
		{
			return contains(impl::narrow(key));
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// @}

		/// \name Erasure
		/// @{

	  private:
		/// \cond

		TOML_EXPORTED_MEMBER_FUNCTION
		map_iterator erase(const_map_iterator) noexcept;

		TOML_EXPORTED_MEMBER_FUNCTION
		map_iterator erase(const_map_iterator, const_map_iterator) noexcept;

		/// \endcond

	  public:
		/// \brief	Removes the specified key-value pair from the table.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// tbl.erase(tbl.begin() + 1);
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = 2, c = 3 }
		/// { a = 1, c = 3 }
		/// \eout
		///
		/// \param 	pos		Iterator to the key-value pair being erased.
		///
		/// \returns Iterator to the first key-value pair immediately following the removed key-value pair.
		iterator erase(iterator pos) noexcept
		{
			return iterator{ erase(const_map_iterator{ pos }) };
		}

		/// \brief	Removes the specified key-value pair from the table (const iterator overload).
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// tbl.erase(tbl.cbegin() + 1);
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = 2, c = 3 }
		/// { a = 1, c = 3 }
		/// \eout
		///
		/// \param 	pos		Iterator to the key-value pair being erased.
		///
		/// \returns Iterator to the first key-value pair immediately following the removed key-value pair.
		iterator erase(const_iterator pos) noexcept
		{
			return iterator{ erase(const_map_iterator{ pos }) };
		}

		/// \brief	Removes the key-value pairs in the range [first, last) from the table.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", "bad" },
		///		{ "c", "karma" },
		///		{ "d", 2 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// tbl.erase(tbl.cbegin() + 1, tbl.cbegin() + 3);
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = "bad", c = "karma", d = 2 }
		/// { a = 1, d = 2 }
		/// \eout
		///
		/// \param 	begin	Iterator to the first key-value pair being erased.
		/// \param 	end		Iterator to the one-past-the-last key-value pair being erased.
		///
		/// \returns Iterator to the first key-value pair immediately following the last removed key-value pair.
		iterator erase(const_iterator begin, const_iterator end) noexcept
		{
			return iterator{ erase(const_map_iterator{ begin }, const_map_iterator{ end }) };
		}

		/// \brief	Removes the value with the given key from the table.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// std::cout << tbl.erase("b") << "\n";
		/// std::cout << tbl.erase("not an existing key") << "\n";
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = 2, c = 3 }
		/// true
		/// false
		/// { a = 1, c = 3 }
		/// \eout
		///
		/// \param 	key		Key to erase.
		///
		/// \returns Number of elements removed (0 or 1).
		TOML_EXPORTED_MEMBER_FUNCTION
		size_t erase(std::string_view key) noexcept;

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Removes the value with the given key from the table.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key		Key to erase.
		///
		/// \returns Number of elements removed (0 or 1).
		size_t erase(std::wstring_view key)
		{
			if (empty())
				return false;

			return erase(impl::narrow(key));
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Removes empty child arrays and tables.
		///
		/// \detail \cpp
		///
		/// auto tbl = toml::table{ { "a", 1 }, { "b", toml::array{ } }, { "c", toml::array{ toml::table{}, toml::array{} } } };
		/// std::cout << arr << "\n";
		///
		/// arr.prune();
		/// std::cout << arr << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = [], c = [ {}, [] ] }
		/// { a = 1 }
		/// \eout
		///
		/// \param recursive Should child arrays and tables themselves be pruned?
		///
		/// \returns A reference to the table.
		TOML_EXPORTED_MEMBER_FUNCTION
		table& prune(bool recursive = true) & noexcept;

		/// \brief	Removes empty child arrays and tables (rvalue overload).
		///
		/// \param recursive Should child arrays and tables themselves be pruned?
		///
		/// \returns An rvalue reference to the table.
		table&& prune(bool recursive = true) && noexcept
		{
			return static_cast<toml::table&&>(this->prune(recursive));
		}

		/// \brief	Removes all key-value pairs from the table.
		TOML_EXPORTED_MEMBER_FUNCTION
		void clear() noexcept;

		/// @}

		/// \name Insertion and Emplacement
		/// @{

	  private:
		/// \cond

		TOML_EXPORTED_MEMBER_FUNCTION
		map_iterator insert_with_hint(const_iterator, key&&, impl::node_ptr&&);

		/// \endcond

	  public:
		/// \brief	Emplaces a new value at a specific key if one did not already exist.
		///
		/// \tparam ValueType	toml::table, toml::array, or any native TOML value type.
		/// \tparam KeyType		A toml::key or any compatible string type.
		/// \tparam ValueArgs	Value constructor argument types.
		/// \param 	hint		Iterator to the position before which the new element will be emplaced.
		/// \param 	key			The key at which to emplace the new value.
		/// \param 	args		Arguments to forward to the value's constructor.
		///
		/// \returns An iterator to the emplacement position (or the position of the value that prevented emplacement)
		///
		/// \note This function has exactly the same semantics as [std::map::emplace_hint()](https://en.cppreference.com/w/cpp/container/map/emplace_hint).
		TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible<KeyType&&> || impl::is_wide_string<KeyType>),
								  typename ValueType = void,
								  typename KeyType,
								  typename... ValueArgs)
		iterator emplace_hint(const_iterator hint, KeyType&& key, ValueArgs&&... args)
		{
			static_assert(!impl::is_wide_string<KeyType> || TOML_ENABLE_WINDOWS_COMPAT,
						  "Emplacement using wide-character keys is only supported on Windows with "
						  "TOML_ENABLE_WINDOWS_COMPAT enabled.");

			using raw_value_type = impl::remove_cvref<ValueType>;
			using value_type	 = std::
				conditional_t<std::is_void_v<raw_value_type>, impl::emplaced_type_of<ValueArgs&&...>, raw_value_type>;

			if constexpr (impl::is_wide_string<KeyType>)
			{
#if TOML_ENABLE_WINDOWS_COMPAT
				return emplace_hint<value_type>(hint,
												impl::narrow(static_cast<KeyType&&>(key)),
												static_cast<ValueArgs&&>(args)...);
#else
				static_assert(impl::always_false<KeyType>, "Evaluated unreachable branch!");
#endif
			}
			else
			{
				static constexpr auto moving_node_ptr = std::is_same_v<value_type, impl::node_ptr> //
													 && sizeof...(ValueArgs) == 1u				   //
													 && impl::first_is_same<impl::node_ptr&&, ValueArgs&&...>;
				using unwrapped_type = impl::remove_cvref<impl::unwrap_node<value_type>>;

				static_assert(moving_node_ptr										//
								  || impl::is_native<unwrapped_type>				//
								  || impl::is_one_of<unwrapped_type, table, array>, //
							  "ValueType argument of table::emplace_hint() must be one "
							  "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST);

				map_iterator ipos = insert_with_hint(hint, toml::key{ static_cast<KeyType&&>(key) }, nullptr);

				// if second is nullptr then we successully claimed the key and inserted the empty sentinel,
				// so now we have to construct the actual value
				if (!ipos->second)
				{
					if constexpr (moving_node_ptr)
						ipos->second = std::move(static_cast<ValueArgs&&>(args)...);
					else
					{
#if TOML_COMPILER_HAS_EXCEPTIONS
						try
						{
#endif
							ipos->second.reset(
								new impl::wrap_node<unwrapped_type>{ static_cast<ValueArgs&&>(args)... });
#if TOML_COMPILER_HAS_EXCEPTIONS
						}
						catch (...)
						{
							erase(const_map_iterator{ ipos }); // strong exception guarantee
							throw;
						}
#endif
					}
				}
				return iterator{ ipos };
			}
		}

		/// \brief	Inserts a new value at a specific key if one did not already exist.
		///
		/// \detail \godbolt{bMnW5r}
		///
		/// \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// for (auto k : { "a", "d" })
		/// {
		///		auto result = tbl.insert(k, 42);
		///		std::cout << "inserted with key '"sv << k << "': "sv << result.second << "\n";
		/// }
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// a = 1
		/// b = 2
		/// c = 3
		///
		/// inserted with key 'a': false
		/// inserted with key 'd': true
		/// a = 1
		/// b = 2
		/// c = 3
		/// d = 42
		/// \eout
		///
		/// \tparam KeyType		A toml::key or any compatible string type.
		/// \tparam ValueType	toml::node, toml::node_view, toml::table, toml::array, or a native TOML value type
		/// 					(or a type promotable to one).
		/// \param 	key			The key at which to insert the new value.
		/// \param 	val			The new value to insert.
		/// \param	flags		Value flags to apply to new values.
		///
		/// \returns	\conditional_return{Valid input}
		/// 			<ul>
		/// 				<li>An iterator to the insertion position (or the position of the value that prevented insertion)
		/// 				<li>A boolean indicating if the insertion was successful.
		/// 			</ul>
		///				\conditional_return{Input is a null toml::node_view}
		/// 			`{ end(), false }`
		///
		/// \attention The return value will always be `{ end(), false }` if the input value was an
		/// 		   null toml::node_view, because no insertion can take place. This is the only circumstance
		/// 		   in which this can occur.
		TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible<KeyType&&> || impl::is_wide_string<KeyType>),
								  typename KeyType,
								  typename ValueType)
		std::pair<iterator, bool> insert(KeyType&& key,
										 ValueType&& val,
										 value_flags flags = preserve_source_value_flags)
		{
			static_assert(!impl::is_wide_string<KeyType> || TOML_ENABLE_WINDOWS_COMPAT,
						  "Insertion using wide-character keys is only supported on Windows with "
						  "TOML_ENABLE_WINDOWS_COMPAT enabled.");

			if constexpr (is_node_view<ValueType>)
			{
				if (!val)
					return { end(), false };
			}

			if constexpr (impl::is_wide_string<KeyType>)
			{
#if TOML_ENABLE_WINDOWS_COMPAT
				return insert(impl::narrow(static_cast<KeyType&&>(key)), static_cast<ValueType&&>(val), flags);
#else
				static_assert(impl::always_false<KeyType>, "Evaluated unreachable branch!");
#endif
			}
			else
			{
				const auto key_view = std::string_view{ key };
				map_iterator ipos	= get_lower_bound(key_view);
				if (ipos == map_.end() || ipos->first != key_view)
				{
					ipos = insert_with_hint(const_iterator{ ipos },
											toml::key{ static_cast<KeyType&&>(key) },
											impl::make_node(static_cast<ValueType&&>(val), flags));
					return { iterator{ ipos }, true };
				}
				return { iterator{ ipos }, false };
			}
		}

		/// \brief	Inserts a series of key-value pairs into the table.
		///
		/// \detail \godbolt{bzYcce}
		///
		/// \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// auto kvps = std::array<std::pair<std::string, int>, 2>{{
		///		{ "d", 42 },
		///		{ "a", 43 } // won't be inserted, 'a' already exists
		///	}};
		///	tbl.insert(kvps.begin(), kvps.end());
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// a = 1
		/// b = 2
		/// c = 3
		///
		/// a = 1
		/// b = 2
		/// c = 3
		/// d = 42
		/// \eout
		///
		/// \tparam Iter	An InputIterator to a collection of key-value pairs.
		/// \param 	begin	An iterator to the first value in the input collection.
		/// \param 	end		An iterator to one-past-the-last value in the input collection.
		/// \param	flags		Value flags to apply to new values.
		///
		/// \remarks This function is morally equivalent to calling `insert(key, value)` for each
		/// 		 key-value pair covered by the iterator range, so any values with keys already found in the
		/// 		 table will not be replaced.
		TOML_CONSTRAINED_TEMPLATE((!is_key_or_convertible<Iter> && !impl::is_wide_string<Iter>), typename Iter)
		void insert(Iter begin, Iter end, value_flags flags = preserve_source_value_flags)
		{
			if (begin == end)
				return;
			for (auto it = begin; it != end; it++)
			{
				if constexpr (std::is_rvalue_reference_v<decltype(*it)>)
					insert(std::move((*it).first), std::move((*it).second), flags);
				else
					insert((*it).first, (*it).second, flags);
			}
		}

		/// \brief	Inserts or assigns a value at a specific key.
		///
		/// \detail \godbolt{ddK563}
		///
		/// \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// for (auto k : { "a", "d" })
		/// {
		///		auto result = tbl.insert_or_assign(k, 42);
		///		std::cout << "value at key '"sv << k
		///			<< "' was "sv << (result.second ? "inserted"sv : "assigned"sv) << "\n";
		/// }
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// a = 1
		/// b = 2
		/// c = 3
		///
		/// value at key 'a' was assigned
		/// value at key 'd' was inserted
		/// a = 42
		/// b = 2
		/// c = 3
		/// d = 42
		/// \eout
		///
		/// \tparam KeyType		A toml::key or any compatible string type.
		/// \tparam ValueType	toml::node, toml::node_view, toml::table, toml::array, or a native TOML value type
		/// 					(or a type promotable to one).
		/// \param 	key			The key at which to insert or assign the value.
		/// \param 	val			The value to insert/assign.
		/// \param	flags		Value flags to apply to new values.
		///
		/// \returns	\conditional_return{Valid input}
		/// 			<ul>
		/// 				<li>An iterator to the value's position
		/// 				<li>`true` if the value was inserted, `false` if it was assigned.
		/// 			</ul>
		/// 			\conditional_return{Input is a null toml::node_view}
		/// 			 `{ end(), false }`
		///
		/// \attention The return value will always be `{ end(), false }` if the input value was
		/// 		   a null toml::node_view, because no insertion or assignment can take place.
		/// 		   This is the only circumstance in which this can occur.
		TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible<KeyType&&> || impl::is_wide_string<KeyType>),
								  typename KeyType,
								  typename ValueType)
		std::pair<iterator, bool> insert_or_assign(KeyType&& key,
												   ValueType&& val,
												   value_flags flags = preserve_source_value_flags)
		{
			static_assert(!impl::is_wide_string<KeyType> || TOML_ENABLE_WINDOWS_COMPAT,
						  "Insertion using wide-character keys is only supported on Windows with "
						  "TOML_ENABLE_WINDOWS_COMPAT enabled.");

			if constexpr (is_node_view<ValueType>)
			{
				if (!val)
					return { end(), false };
			}

			if constexpr (impl::is_wide_string<KeyType>)
			{
#if TOML_ENABLE_WINDOWS_COMPAT
				return insert_or_assign(impl::narrow(static_cast<KeyType&&>(key)),
										static_cast<ValueType&&>(val),
										flags);
#else
				static_assert(impl::always_false<KeyType>, "Evaluated unreachable branch!");
#endif
			}
			else
			{
				const auto key_view = std::string_view{ key };
				map_iterator ipos	= get_lower_bound(key_view);
				if (ipos == map_.end() || ipos->first != key_view)
				{
					ipos = insert_with_hint(const_iterator{ ipos },
											toml::key{ static_cast<KeyType&&>(key) },
											impl::make_node(static_cast<ValueType&&>(val), flags));
					return { iterator{ ipos }, true };
				}
				else
				{
					(*ipos).second = impl::make_node(static_cast<ValueType&&>(val), flags);
					return { iterator{ ipos }, false };
				}
			}
		}

		/// \brief	Emplaces a new value at a specific key if one did not already exist.
		///
		/// \detail \cpp
		/// auto tbl = toml::table{
		///		{ "a", 1 },
		///		{ "b", 2 },
		///		{ "c", 3 }
		///	};
		/// std::cout << tbl << "\n";
		///
		/// for (auto k : { "a", "d" })
		/// {
		///		// add a string using std::string's substring constructor
		///		auto result = tbl.emplace<std::string>(k, "this is not a drill"sv, 14, 5);
		///		std::cout << "emplaced with key '"sv << k << "': "sv << result.second << "\n";
		/// }
		/// std::cout << tbl << "\n";
		/// \ecpp
		///
		/// \out
		/// { a = 1, b = 2, c = 3 }
		/// emplaced with key 'a': false
		/// emplaced with key 'd': true
		/// { a = 1, b = 2, c = 3, d = "drill" }
		/// \eout
		///
		/// \tparam ValueType	toml::table, toml::array, or any native TOML value type.
		/// \tparam KeyType		A toml::key or any compatible string type.
		/// \tparam ValueArgs	Value constructor argument types.
		/// \param 	key			The key at which to emplace the new value.
		/// \param 	args		Arguments to forward to the value's constructor.
		///
		/// \returns A std::pair containing: <br>
		/// 		- An iterator to the emplacement position (or the position of the value that prevented emplacement)
		/// 		- A boolean indicating if the emplacement was successful.
		///
		/// \remark There is no difference between insert() and emplace() for trivial value types (floats, ints, bools).
		TOML_CONSTRAINED_TEMPLATE((is_key_or_convertible<KeyType&&> || impl::is_wide_string<KeyType>),
								  typename ValueType = void,
								  typename KeyType,
								  typename... ValueArgs)
		std::pair<iterator, bool> emplace(KeyType&& key, ValueArgs&&... args)
		{
			static_assert(!impl::is_wide_string<KeyType> || TOML_ENABLE_WINDOWS_COMPAT,
						  "Emplacement using wide-character keys is only supported on Windows with "
						  "TOML_ENABLE_WINDOWS_COMPAT enabled.");

			using raw_value_type = impl::remove_cvref<ValueType>;
			using value_type	 = std::
				conditional_t<std::is_void_v<raw_value_type>, impl::emplaced_type_of<ValueArgs&&...>, raw_value_type>;

			if constexpr (impl::is_wide_string<KeyType>)
			{
#if TOML_ENABLE_WINDOWS_COMPAT
				return emplace<value_type>(impl::narrow(static_cast<KeyType&&>(key)),
										   static_cast<ValueArgs&&>(args)...);
#else
				static_assert(impl::always_false<KeyType>, "Evaluated unreachable branch!");
#endif
			}
			else
			{
				using unwrapped_type = impl::remove_cvref<impl::unwrap_node<value_type>>;
				static_assert((impl::is_native<unwrapped_type> || impl::is_one_of<unwrapped_type, table, array>),
							  "ValueType argument of table::emplace() must be one "
							  "of:" TOML_SA_UNWRAPPED_NODE_TYPE_LIST);

				const auto key_view = std::string_view{ key };
				auto ipos			= get_lower_bound(key_view);
				if (ipos == map_.end() || ipos->first != key_view)
				{
					ipos = insert_with_hint(
						const_iterator{ ipos },
						toml::key{ static_cast<KeyType&&>(key) },
						impl::node_ptr{ new impl::wrap_node<unwrapped_type>{ static_cast<ValueArgs&&>(args)... } });
					return { iterator{ ipos }, true };
				}
				return { iterator{ ipos }, false };
			}
		}

		/// @}

		/// \name Node views
		/// @{

		/// \cond
		using node::operator[]; // inherit operator[toml::path]
		/// \endcond

		/// \brief	Gets a node_view for the selected value.
		///
		/// \param 	key The key used for the lookup.
		///
		/// \returns	A view of the value at the given key if one existed, or an empty node view.
		///
		/// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it
		/// 		 didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it.
		/// 		 <strong>This is not an error.</strong>
		///
		/// \see toml::node_view
		TOML_NODISCARD
		node_view<node> operator[](std::string_view key) noexcept
		{
			return node_view<node>{ get(key) };
		}

		/// \brief	Gets a node_view for the selected value (const overload).
		///
		/// \param 	key The key used for the lookup.
		///
		/// \returns	A view of the value at the given key if one existed, or an empty node view.
		///
		/// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it
		/// 		 didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it.
		/// 		 <strong>This is not an error.</strong>
		///
		/// \see toml::node_view
		TOML_NODISCARD
		node_view<const node> operator[](std::string_view key) const noexcept
		{
			return node_view<const node>{ get(key) };
		}

#if TOML_ENABLE_WINDOWS_COMPAT

		/// \brief	Gets a node_view for the selected value.
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key The key used for the lookup.
		///
		/// \returns	A view of the value at the given key if one existed, or an empty node view.
		///
		/// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it
		/// 		 didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it.
		/// 		 <strong>This is not an error.</strong>
		///
		/// \see toml::node_view
		TOML_NODISCARD
		node_view<node> operator[](std::wstring_view key)
		{
			return node_view<node>{ get(key) };
		}

		/// \brief	Gets a node_view for the selected value (const overload).
		///
		/// \availability This overload is only available when #TOML_ENABLE_WINDOWS_COMPAT is enabled.
		///
		/// \param 	key The key used for the lookup.
		///
		/// \returns	A view of the value at the given key if one existed, or an empty node view.
		///
		/// \remarks std::map::operator[]'s behaviour of default-constructing a value at a key if it
		/// 		 didn't exist is a crazy bug factory so I've deliberately chosen not to emulate it.
		/// 		 <strong>This is not an error.</strong>
		///
		/// \see toml::node_view
		TOML_NODISCARD
		node_view<const node> operator[](std::wstring_view key) const
		{
			return node_view<const node>{ get(key) };
		}

#endif // TOML_ENABLE_WINDOWS_COMPAT

		/// @}

		/// \name Equality
		/// @{

	  private:
		/// \cond

		TOML_PURE_GETTER
		TOML_EXPORTED_STATIC_FUNCTION
		static bool TOML_CALLCONV equal(const table&, const table&) noexcept;

		/// \endcond
	  public:
		/// \brief	Equality operator.
		///
		/// \param 	lhs	The LHS table.
		/// \param 	rhs	The RHS table.
		///
		/// \returns	True if the tables contained the same keys and map.
		TOML_NODISCARD
		friend bool operator==(const table& lhs, const table& rhs) noexcept
		{
			return equal(lhs, rhs);
		}

		/// \brief	Inequality operator.
		///
		/// \param 	lhs	The LHS table.
		/// \param 	rhs	The RHS table.
		///
		/// \returns	True if the tables did not contain the same keys and map.
		TOML_NODISCARD
		friend bool operator!=(const table& lhs, const table& rhs) noexcept
		{
			return !equal(lhs, rhs);
		}

		/// @}

#if TOML_ENABLE_FORMATTERS

		/// \brief	Prints the table out to a stream as formatted TOML.
		///
		/// \availability This operator is only available when #TOML_ENABLE_FORMATTERS is enabled.
		friend std::ostream& operator<<(std::ostream& lhs, const table& rhs)
		{
			impl::print_to_stream(lhs, rhs);
			return lhs;
		}

#endif
	};
}
TOML_NAMESPACE_END;

#include "header_end.hpp"
