#pragma once

#include <QQmlParserStatus>
#include <QElapsedTimer>
#include <QEasingCurve>
#include <QList>
#include <QPointF>
#include <QQuickItem>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QVariantAnimation>
#include <QVariantList>
#include <QVector>
#include <qtypes.h>
#include <qvectornd.h>

namespace ZShell::components {

class CarouselView : public QQuickItem {
	Q_OBJECT
	QML_ELEMENT

	Q_PROPERTY(
		QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
			delegateChanged)
	Q_PROPERTY(QVariantList model READ model WRITE setModel NOTIFY modelChanged)

	Q_PROPERTY(
		qreal focalWidth READ focalWidth WRITE setFocalWidth NOTIFY
			focalWidthChanged)
	Q_PROPERTY(
		qreal minEdgeWidth READ minEdgeWidth WRITE setMinEdgeWidth NOTIFY
			minEdgeWidthChanged)
	Q_PROPERTY(
		qreal itemSpacing READ itemSpacing WRITE setItemSpacing NOTIFY
			itemSpacingChanged)
	Q_PROPERTY(
		qreal tileHeight READ tileHeight WRITE setTileHeight NOTIFY
			tileHeightChanged)
	Q_PROPERTY(
		qreal maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged)

	Q_PROPERTY(
		int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY
			currentIndexChanged)

	Q_PROPERTY(
		QQuickItem* currentItem READ currentItem NOTIFY currentItemChanged)

	Q_PROPERTY(
		int glideDuration READ glideDuration WRITE setGlideDuration NOTIFY
			glideDurationChanged)
	Q_PROPERTY(
		QEasingCurve::Type glideEasingType READ glideEasingType WRITE
			setGlideEasingType NOTIFY glideEasingTypeChanged)

	Q_PROPERTY(
		QVariantMap delegateProperties READ delegateProperties WRITE
			setDelegateProperties NOTIFY delegatePropertiesChanged)

	public:
	explicit CarouselView(QQuickItem* parent = nullptr);
	~CarouselView() override;

	QQmlComponent* delegate() const { return m_delegate; }
	void setDelegate(QQmlComponent* c);

	QVariantList model() const { return m_model; }
	void setModel(const QVariantList& m);

	qreal focalWidth() const { return m_focalWidth; }
	void setFocalWidth(qreal v);

	qreal minEdgeWidth() const { return m_minEdgeWidth; }
	void setMinEdgeWidth(qreal v);

	qreal itemSpacing() const { return m_itemSpacing; }
	void setItemSpacing(qreal v);

	qreal tileHeight() const { return m_tileHeight; }
	void setTileHeight(qreal v);

	qreal maxWidth() const { return m_maxWidth; }
	void setMaxWidth(qreal v);

	int currentIndex() const { return m_currentRealIndex; }
	void setCurrentIndex(int idx);

	QQuickItem* currentItem() const { return m_currentItem; }

	int glideDuration() const { return m_glideDuration; }
	void setGlideDuration(int v);

	QEasingCurve::Type glideEasingType() const { return m_glideEasingType; }
	void setGlideEasingType(QEasingCurve::Type t);

	QVariantMap delegateProperties() const { return m_delegateProperties; }
	void setDelegateProperties(const QVariantMap& v);

	Q_INVOKABLE void goToIndex(int realIndex);
	Q_INVOKABLE void incrementCurrentIndex();
	Q_INVOKABLE void decrementCurrentIndex();

	signals:
	void delegateChanged();
	void modelChanged();
	void focalWidthChanged();
	void minEdgeWidthChanged();
	void itemSpacingChanged();
	void tileHeightChanged();
	void maxWidthChanged();
	void currentIndexChanged();
	void currentItemChanged();
	void glideDurationChanged();
	void glideEasingTypeChanged();
	void delegatePropertiesChanged();

	void activated(int realIndex, QVariant modelData);
	void previewIndexChanged(int realIndex, QVariant modelData);

	protected:
	void classBegin() override;
	void componentComplete() override;
	bool childMouseEventFilter(QQuickItem* item, QEvent* event) override;
	void geometryChange(const QRectF& newGeo, const QRectF& oldGeo) override;
	void mousePressEvent(QMouseEvent* event) override;
	void mouseMoveEvent(QMouseEvent* event) override;
	void mouseReleaseEvent(QMouseEvent* event) override;
	void mouseUngrabEvent() override;
	void wheelEvent(QWheelEvent* event) override;

	private slots:
	void handleDelegateActivate();

	private:
	struct Arrangement {
		int n = 0;
		qreal width = 0;
		qreal outerSpacing = 0;

		QVector<qreal> sizes;
		QVector<qreal> centers;

		qreal effectiveExtraWidth = 0;
	};

	struct Slot {
		QQuickItem* item = nullptr;
		int virtualIndex = 0;
		int realIndex = -1;
		bool active = false;
	};

	struct VelocitySample {
		qint64 t;
		qreal x;
	};

	void rebuildArrangement();
	void relayout();

	Arrangement arrangementForCount(int n) const;

	int layoutCapacityForWidth(qreal width) const;
	qreal targetWidthForMaxWidth(qreal maxWidth) const;

	void updateSlotContent(Slot& slot, int virtualIndex);

	void positionSlot(const Slot& slot) const;

	void applyPropertiesToItem(QQuickItem* item, int realIndex) const;

	qreal pitch() const { return m_focalWidth + m_itemSpacing; }

	qreal contentXForIndex(qreal index) const;
	int indexForContentX(qreal x) const;
	qreal snapTargetX(qreal x) const;

	void snapToNearest();
	void glideTo(qreal dest);
	void cancelAnimations();

	qreal keylineSize(int io) const;
	qreal sampleSize(qreal childLoc) const;
	qreal continuousIndex() const;

	int realIndexOf(int virtualIndex) const;

	void updateCurrentIndexFromContentX(bool emitPreview);

	void updateCurrentItem();

	void wrapContentIfNeeded();
	void centerInstantlyOnReal(int realIndex);

	QQuickItem* acquireItem();
	void releaseAllItems();

	void addVelocitySample(qint64 t, qreal x);
	qreal sampledVelocity() const;

	void notifyCurrentIndexChanged();
	void notifyCurrentItemChanged();
	void notifyPreviewIndexChanged(int realIndex);

	QQmlComponent* m_delegate = nullptr;
	QVariantList m_model;
	QVariantMap m_delegateProperties;

	qreal m_focalWidth = 0;
	qreal m_minEdgeWidth = 56;
	qreal m_itemSpacing = 8;
	qreal m_tileHeight = 0;
	qreal m_maxWidth = 0;

	Arrangement m_arrangement;

	int m_layoutCapacity = 0;

	qreal m_contentX = 0;
	int m_currentVirtualIndex = 0;
	int m_currentRealIndex = -1;
	int m_previewRealIndex = -1;

	QQuickItem* m_currentItem = nullptr;

	int m_centerBlockStart = 0;

	QList<Slot> m_slots;

	int m_glideDuration = 250;
	QEasingCurve::Type m_glideEasingType = QEasingCurve::OutCubic;
	QVariantAnimation* m_glideAnim = nullptr;

	bool m_dragging = false;
	bool m_dragActive = false;

	QPointF m_pressPos;
	qreal m_pressContentX = 0;

	QElapsedTimer m_dragTimer;

	QVector<VelocitySample> m_velocitySamples;
	static constexpr qint64 kVelocitySampleWindowMs = 80;

	QVariantAnimation* m_flickAnim = nullptr;

	bool m_completed = false;
	int m_pendingCurrentIndex = -1;

	bool m_initializedLayout = false;
	bool m_relayoutQueued = false;
};

} // namespace ZShell::components
