#pragma once

#include "confignode.hpp"

#include <QJsonObject>
#include <QMetaProperty>
#include <QMetaType>
#include <QObject>
#include <QSet>
#include <QStringList>
#include <QVariant>
#include <QVariantList>

namespace ZShell::config {

inline QVariantMap vmap(
	std::initializer_list<std::pair<QString, QVariant>> entries) {
	QVariantMap map;
	for (const auto& [key, value] : entries)
		map.insert(key, value);
	return map;
}

#define CFG_PROPERTY(Type, name, ...)                                          \
	Q_PROPERTY(Type name READ name WRITE set_##name NOTIFY name##Changed)      \
	public:                                                                    \
	[[nodiscard]] Type name() const {                                          \
		return m_##name;                                                       \
	}                                                                          \
	void set_##name(const Type& value) {                                       \
		if (ConfigObject::updateMember(m_##name, value)) {                     \
			markPropertyLoaded(QStringLiteral(#name));                         \
			Q_EMIT name##Changed();                                            \
			notifyPropertyChanged(                                             \
				QStringLiteral(#name), QVariant::fromValue(m_##name));         \
		}                                                                      \
	}                                                                          \
	Q_SIGNAL void name##Changed();                                             \
                                                                               \
	private:                                                                   \
	Type m_##name __VA_OPT__(= __VA_ARGS__);

#define CONFIG_SUBOBJECT(Type, name)                                           \
	Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT)                  \
                                                                               \
	public:                                                                    \
	[[nodiscard]] Type* name() const {                                         \
		return m_##name;                                                       \
	}                                                                          \
                                                                               \
	private:                                                                   \
	Type* m_##name = nullptr;

class ConfigObject : public ConfigNode {
	Q_OBJECT

	public:
	explicit ConfigObject(QObject* parent = nullptr);

	void loadFromJson(const QJsonValue& json) override;
	[[nodiscard]] QJsonValue toJson() const override;
	void clearLoadedKeys() override;
	[[nodiscard]] QStringList unknownKeys() const override;
	[[nodiscard]] QList<ConfigNode*> childNodes() const override;
	void materializeDefaults() override;
	void resyncFromGlobal() override;

	[[nodiscard]] virtual QStringList identityKeys() const;

	[[nodiscard]] bool isPropertyLoaded(const QString& name) const;
	[[nodiscard]] bool isGlobalOnly(const QString& name) const;
	[[nodiscard]] QStringList globalOnlyKeys() const;

	Q_INVOKABLE void resetOption(const QString& name);

	template <typename T> static bool updateMember(T& member, const T& value) {
		if constexpr (std::is_floating_point_v<T>) {
			if (qFuzzyCompare(member + 1.0, value + 1.0)) return false;
		} else {
			if (member == value) return false;
		}
		member = value;
		return true;
	}

	protected:
	void syncValuesFromGlobal() override;
	void onGlobalPropertiesChanged(
		const QMap<QString, QVariant>& changed) override;
	[[nodiscard]] QString childPath(const ConfigNode* child) const override;

	void markPropertyLoaded(const QString& name);
	void markGlobalOnly(const QString& name);

	private:
	QSet<QString> m_loadedKeys;
	QSet<QString> m_globalOnlyKeys;
	QJsonObject m_extras;
};

} // namespace ZShell::config
