#include "parquet_shredding.hpp"
#include "duckdb/common/exception/binder_exception.hpp"
#include "duckdb/common/type_visitor.hpp"

namespace duckdb {

ChildShreddingTypes::ChildShreddingTypes() : types(make_uniq<case_insensitive_map_t<ShreddingType>>()) {
}

ChildShreddingTypes ChildShreddingTypes::Copy() const {
	ChildShreddingTypes result;
	for (const auto &type : *types) {
		result.types->emplace(type.first, type.second.Copy());
	}
	return result;
}

ShreddingType::ShreddingType() : set(false) {
}

ShreddingType::ShreddingType(const LogicalType &type) : set(true), type(type) {
}

bool ShreddingType::operator==(const ShreddingType &other) const {
	if (set != other.set) {
		return false;
	}
	if (!set) {
		return true;
	}
	if (type != other.type) {
		return false;
	}
	if (!children.types && !other.children.types) {
		return true;
	}
	if (!children.types || !other.children.types) {
		return false;
	}
	auto &a_children = *children.types;
	auto &b_children = *other.children.types;
	if (a_children.size() != b_children.size()) {
		return false;
	}
	for (auto a_it = a_children.begin(); a_it != a_children.end(); ++a_it) {
		auto &a_name = a_it->first;
		auto b_it = b_children.find(a_name);
		if (b_it == b_children.end()) {
			return false;
		}

		auto &a_child = a_it->second;
		auto &b_child = b_it->second;
		if (!(a_child == b_child)) {
			return false;
		}
	}
	return true;
}

ShreddingType ShreddingType::Copy() const {
	auto result = set ? ShreddingType(type) : ShreddingType();
	result.children = children.Copy();
	return result;
}

static ShreddingType ConvertShreddingTypeRecursive(const LogicalType &type) {
	if (type.id() == LogicalTypeId::VARIANT) {
		return ShreddingType(LogicalType(LogicalTypeId::ANY));
	}
	if (!type.IsNested()) {
		return ShreddingType(type);
	}

	switch (type.id()) {
	case LogicalTypeId::STRUCT: {
		ShreddingType res(type);
		auto &children = StructType::GetChildTypes(type);
		for (auto &entry : children) {
			res.AddChild(entry.first, ConvertShreddingTypeRecursive(entry.second));
		}
		return res;
	}
	case LogicalTypeId::LIST: {
		ShreddingType res(type);
		const auto &child = ListType::GetChildType(type);
		res.AddChild("element", ConvertShreddingTypeRecursive(child));
		return res;
	}
	default:
		break;
	}
	throw BinderException("VARIANT can only be shredded on LIST/STRUCT/ANY/non-nested type, not %s", type.ToString());
}

void ShreddingType::AddChild(const string &name, ShreddingType &&child) {
	children.types->emplace(name, std::move(child));
}

optional_ptr<const ShreddingType> ShreddingType::GetChild(const string &name) const {
	auto it = children.types->find(name);
	if (it == children.types->end()) {
		return nullptr;
	}
	return it->second;
}

ShreddingType ShreddingType::GetShreddingTypes(const Value &val, ClientContext &context) {
	if (val.type().id() != LogicalTypeId::VARCHAR) {
		throw BinderException("SHREDDING value should be of type VARCHAR, a stringified type to use for the column");
	}
	auto type_str = val.GetValue<string>();
	auto logical_type = TransformStringToLogicalType(type_str, context);

	return ConvertShreddingTypeRecursive(logical_type);
}

} // namespace duckdb
