pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Shapes
import ZShell.Config
import qs.Services

TextFieldBase {
	id: root

	enum TextFieldType {
		Outlined,
		Filled
	}

	readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius)
	readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText
	property bool emptyIsValid: true
	property string errorText
	readonly property int filledOffset: type === CustomTextField.Filled ? Tokens.spacing.small : 0
	readonly property int horizontalPadding: Tokens.padding.large
	property bool isError
	property string leadingIcon
	readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0
	property int radius: Tokens.rounding.small
	readonly property real smallFontScale: smallFontSize / font.pointSize
	property int smallFontSize: Tokens.font.size.small
	property string supportingText
	readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Tokens.spacing.extraSmall : 0
	property string trailingIcon
	readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0
	property int type: CustomTextField.Outlined
	readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text))
	property var validate // Regex or function

	bottomPadding: Tokens.padding.large + supportingTextOffset - filledOffset
	leftPadding: horizontalPadding + leadingOffset
	rightPadding: horizontalPadding + trailingOffset
	topPadding: Tokens.padding.large + filledOffset

	background: Loader {
		anchors.bottomMargin: root.supportingTextOffset
		anchors.fill: parent
		sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp

		StateLayer {
			id: stateLayer

			cursorShape: Qt.IBeamCursor
			enabled: !root.activeFocus
			manualPressOverride: tapHandler.pressed
			radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0
			topLeftRadius: root.clampedRadius
			topRightRadius: root.clampedRadius

			onClicked: root.focus = true
		}
	}

	onEditingFinished: {
		if (!valid)
			isError = true;
	}
	onPressed: {
		if (stateLayer.enabled)
			stateLayer.press(stateLayer.mouseX, stateLayer.mouseY);
	}
	onTextEdited: {
		if (isError)
			isError = false;
	}

	Item {
		id: contentWrapper

		anchors.bottomMargin: root.supportingTextOffset
		anchors.fill: parent

		CustomText {
			id: placeholder

			anchors.left: parent.left
			anchors.leftMargin: root.leftPadding
			anchors.topMargin: Tokens.padding.extraSmall
			anchors.verticalCenter: parent.verticalCenter
			color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : root.text ? Colors.palette.m3outline : root.placeholderTextColor)
			font.family: root.font.family
			font.pointSize: root.font.pointSize
			font.variableAxes: root.font.variableAxes
			font.weight: root.font.weight
			renderType: Text.QtRendering
			text: root.placeholderText

			states: [
				State {
					name: "smallOutlined"
					when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text)

					PropertyChanges {
						placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding
						placeholder.scale: root.smallFontScale
					}

					AnchorChanges {
						anchors.verticalCenter: contentWrapper.top
						target: placeholder
					}
				},
				State {
					name: "smallFilled"
					when: root.type === CustomTextField.Filled && (root.activeFocus || root.text)

					PropertyChanges {
						placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset
						placeholder.scale: root.smallFontScale
					}

					AnchorChanges {
						anchors.top: contentWrapper.top
						anchors.verticalCenter: undefined
						target: placeholder
					}
				}
			]
			transitions: Transition {
				Anim {
					properties: "scale,leftMargin"
					type: Anim.DefaultEffects
				}

				AnchorAnim {
					duration: Tokens.anim.durations.expressiveDefaultEffects
					easing: Tokens.anim.expressiveDefaultEffects
				}
			}
		}

		Loader {
			id: leadingIconLoader

			active: root.leadingIcon
			anchors.left: parent.left
			anchors.leftMargin: Tokens.padding.small
			anchors.verticalCenter: parent.verticalCenter

			sourceComponent: MaterialIcon {
				color: Colors.palette.m3onSurfaceVariant
				text: root.leadingIcon
			}
		}

		Loader {
			id: trailingIconLoader

			active: root.trailingIcon
			anchors.right: parent.right
			anchors.rightMargin: Tokens.padding.small
			anchors.verticalCenter: parent.verticalCenter

			sourceComponent: MaterialIcon {
				color: Colors.palette.m3onSurfaceVariant
				text: root.trailingIcon
			}
		}
	}

	Loader {
		id: supportingTextLoader

		active: root.effectiveSupportingText
		anchors.bottom: parent.bottom
		anchors.left: parent.left
		anchors.leftMargin: root.horizontalPadding

		sourceComponent: CustomText {
			color: root.isError ? Colors.palette.m3error : Colors.palette.m3onSurfaceVariant
			font.pointSize: Tokens.font.size.small
			text: root.effectiveSupportingText
		}
	}

	TapHandler {
		id: tapHandler
	}

	Component {
		id: outlineComp

		Shape {
			id: bg

			asynchronous: true
			preferredRendererType: Shape.CurveRenderer

			ShapePath {
				id: path

				readonly property real inset: strokeWidth / 2
				readonly property real outlineGap: placeholder.width * root.smallFontScale + Tokens.spacing.small * 2
				property real outlineGapScale: root.activeFocus || root.text ? 1 : 0

				capStyle: ShapePath.RoundCap
				fillColor: "transparent"
				startX: path.inset + root.horizontalPadding - root.clampedRadius + path.outlineGap * (1 - path.outlineGapScale) / 2 + path.outlineGap * path.outlineGapScale
				startY: path.inset
				strokeColor: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
				strokeWidth: root.activeFocus ? 2 : 1

				Behavior on outlineGapScale {
					Anim {
						type: Anim.DefaultEffects
					}
				}
				Behavior on strokeColor {
					CAnim {
					}
				}
				Behavior on strokeWidth {
					Anim {
					}
				}

				PathLine {
					x: bg.width - path.inset - root.clampedRadius
					y: path.inset
				}

				PathArc {
					radiusX: root.clampedRadius
					radiusY: root.clampedRadius
					x: bg.width - path.inset
					y: path.inset + root.clampedRadius
				}

				PathLine {
					x: bg.width - path.inset
					y: bg.height - path.inset - root.clampedRadius
				}

				PathArc {
					radiusX: root.clampedRadius
					radiusY: root.clampedRadius
					x: bg.width - path.inset - root.clampedRadius
					y: bg.height - path.inset
				}

				PathLine {
					x: path.inset + root.clampedRadius
					y: bg.height - path.inset
				}

				PathArc {
					radiusX: root.clampedRadius
					radiusY: root.clampedRadius
					x: path.inset
					y: bg.height - path.inset - root.clampedRadius
				}

				PathLine {
					x: path.inset
					y: path.inset + root.clampedRadius
				}

				PathArc {
					radiusX: root.clampedRadius
					radiusY: root.clampedRadius
					x: path.inset + root.clampedRadius
					y: path.inset
				}

				PathLine {
					x: path.inset + root.horizontalPadding - root.clampedRadius / 2 + path.outlineGap * (1 - path.outlineGapScale) / 2
					y: path.inset
				}
			}
		}
	}

	Component {
		id: filledComp

		CustomRect {
			color: root.activeFocus ? Colors.tPalette.m3surfaceContainerHighest : Colors.tPalette.m3surfaceContainerHigh
			topLeftRadius: root.clampedRadius
			topRightRadius: root.clampedRadius

			CustomRect {
				anchors.bottom: parent.bottom
				anchors.left: parent.left
				anchors.right: parent.right
				color: root.isError ? Colors.palette.m3error : (root.activeFocus ? Colors.palette.m3primary : Colors.palette.m3outline)
				implicitHeight: root.activeFocus ? 2 : 1

				Behavior on implicitHeight {
					Anim {
					}
				}
			}
		}
	}
}
