#pragma once

#include "configobject.hpp"

#include <QJsonArray>
#include <QJsonObject>
#include <QQmlEngine>
#include <QVariantList>

namespace ZShell::config {

class ConfigList : public ConfigNode {
	Q_OBJECT
	QML_ANONYMOUS

	Q_PROPERTY(int count READ count NOTIFY countChanged)
	Q_PROPERTY(QVariantList values READ values NOTIFY valuesChanged)

	public:
	explicit ConfigList(
		QObject* parent = nullptr, const QVariantList& defaults = {});

	[[nodiscard]] int count() const;
	[[nodiscard]] QVariantList values() const;
	[[nodiscard]] ConfigObject* itemAt(int index) const;
	[[nodiscard]] const QList<ConfigObject*>& items() const;

	Q_INVOKABLE void remove(int index);
	Q_INVOKABLE void move(int from, int to);
	Q_INVOKABLE void clear();

	void loadFromJson(const QJsonValue& json) override;
	[[nodiscard]] QJsonValue toJson() const override;
	void clearLoadedKeys() override;
	[[nodiscard]] QStringList unknownKeys() const override;
	void materializeDefaults() override;
	void resyncFromGlobal() override;

	signals:
	void countChanged();
	void valuesChanged();

	protected:
	virtual ConfigObject* createItem() = 0;

	void resetToDefaults();

	ConfigObject* insertItem(const QVariantMap& props, int index = -1);

	[[nodiscard]] QJsonArray elementsToJson() const;

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

	private:
	void populate(const QJsonArray& arr);
	static bool sameElement(
		const ConfigObject* item,
		const QJsonValue& current,
		const QJsonValue& json);
	void appendItem(const QJsonValue& json);
	void destroyItems();
	void destroyItem(ConfigObject* item);
	void rejectGlobalOnlyElement(const ConfigObject* item) const;
	void connectItem(ConfigNode* node);
	void disconnectItem(ConfigNode* node);
	void onItemChanged();
	void notifyChanged();

	QJsonArray m_defaults;
	QList<ConfigObject*> m_items;
	bool m_loaded = false;
};

} // namespace ZShell::config

#define CONFIG_LIST_TYPE(Element, Name)                                        \
	class Name : public ZShell::config::ConfigList {                           \
		Q_OBJECT                                                               \
		QML_ANONYMOUS                                                          \
                                                                               \
		public:                                                                \
		explicit Name(                                                         \
			QObject* parent = nullptr, const QVariantList& defaults = {})      \
			: ZShell::config::ConfigList(parent, defaults) {                   \
			resetToDefaults();                                                 \
		}                                                                      \
                                                                               \
		Q_INVOKABLE ZShell::config::Element* at(int index) const {             \
			return static_cast<ZShell::config::Element*>(itemAt(index));       \
		}                                                                      \
		Q_INVOKABLE ZShell::config::Element* insert(                           \
			const QVariantMap& props, int index = -1) {                        \
			return static_cast<ZShell::config::Element*>(                      \
				insertItem(props, index));                                     \
		}                                                                      \
                                                                               \
		protected:                                                             \
		[[nodiscard]] ZShell::config::ConfigObject* createItem() override {    \
			return new ZShell::config::Element(this);                          \
		}                                                                      \
	};

#define CONFIG_LIST(Type, name, ...)                                           \
	Q_PROPERTY(ZShell::config::Type* name READ name CONSTANT)                  \
                                                                               \
	public:                                                                    \
	[[nodiscard]] Type* name() const {                                         \
		return m_##name;                                                       \
	}                                                                          \
                                                                               \
	private:                                                                   \
	Type* m_##name = new Type(this __VA_OPT__(, __VA_ARGS__));

#define LIST_ENTRY(id, enabled)                                                \
	vmap({{"id", QString::fromUtf8(#id)}, {"enabled", enabled}})

namespace ZShell::config {

class ListEntry : public ConfigObject {
	Q_OBJECT
	QML_ANONYMOUS

	CFG_PROPERTY(QString, id)
	CFG_PROPERTY(bool, enabled, true)

	public:
	explicit ListEntry(QObject* parent = nullptr) : ConfigObject(parent) {}

	[[nodiscard]] QStringList identityKeys() const override {
		return {QStringLiteral("id")};
	}
};

CONFIG_LIST_TYPE(ListEntry, EntryList)

} // namespace ZShell::config
