import QtQuick
import QtQuick.Effects
import QtQuick.Shapes
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import ZShell.Llm
import qs.Components
import qs.Services

CustomRect {
	id: root

	required property string language
	required property string code
	property bool copied: false
	property color codeBackgroundColor: Colors.palette.m3surfaceContainerHigh
	property color codeHeaderColor: Colors.palette.m3outline
	property var codeSpans: []
	property int highlightToken: 0

	function refresh() {
		const token = ++root.highlightToken;
		CodeHighlighter.highlight(root.code, root.language, root, token);
	}

	function onHighlightSpans(token, spans) {
		if (token !== root.highlightToken)
			return;
		codeSpans = spans;
	}

	function roleColor(kind) {
		var s = CodeColors.active;
		switch (kind) {
		case "comment":
			return s.comment;
		case "string":
			return s.string;
		case "string.key":
			return s.stringKey;
		case "number":
		case "constant":
			return s.number;
		case "keyword":
			return s.keyword;
		case "type":
			return s.type;
		case "function":
			return s.functions;
		case "method":
			return s.method ?? s.functions;
		case "macro":
			return s.macro;
		case "preproc":
			return s.preproc ?? s.macro;
		case "operator":
			return s.operator ?? s.normal;
		case "property":
			return s.property ?? s.normal;
		case "label":
			return s.label;
		case "attribute":
			return s.attribute ?? s.label;
		default:
			return s.normal;
		}
	}

	function escapeHtml(text): string {
		return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
	}

	function highlightedHtml(code, spans): string {
		let out;
		if (!spans.length) {
			out = escapeHtml(code);
		} else {
			out = "";
			let pos = 0;
			for (let i = 0; i < spans.length; i++) {
				const span = spans[i];
				const start = Math.min(span.start, code.length);
				const end = Math.min(span.start + span.length, code.length);
				if (end <= pos)
					continue;
				if (start > pos)
					out += escapeHtml(code.slice(pos, start));
				out += `<font color="${roleColor(span.kind)}">` + escapeHtml(code.slice(start, end)) + "</font>";
				pos = end;
			}
			if (pos < code.length)
				out += escapeHtml(code.slice(pos));
		}
		return out.replace(/(^|\n)[ \t]+/g, ws => ws.replace(/[ \t]/g, "&nbsp;")).replace(/\n/g, "<br>");
	}

	implicitWidth: headerRow.implicitWidth + headerRow.anchors.leftMargin + headerRow.anchors.rightMargin
	implicitHeight: headerRow.anchors.topMargin + headerRow.implicitHeight + codeRect.implicitHeight + codeRect.anchors.margins + codeRect.anchors.topMargin
	color: root.codeBackgroundColor
	radius: Tokens.rounding.medium

	onLanguageChanged: {
		codeSpans = [];
		refresh();
	}
	onCodeChanged: refresh()

	RowLayout {
		id: headerRow

		anchors.top: parent.top
		anchors.left: parent.left
		anchors.right: parent.right
		anchors.topMargin: Tokens.padding.small
		anchors.leftMargin: Tokens.padding.small
		anchors.rightMargin: Tokens.padding.small
		spacing: Tokens.spacing.small

		Item {
			id: iconItem

			readonly property string iconPath: CodeIcons.path(root.language)

			Layout.fillHeight: true
			implicitWidth: height

			Shape {
				id: shape

				anchors.centerIn: parent
				visible: iconItem.iconPath !== ""
				preferredRendererType: Shape.CurveRenderer
				scale: Math.min(langText.implicitHeight / height, langText.implicitHeight / width)

				ShapePath {
					strokeColor: "transparent"
					fillColor: Colors.palette.m3tertiary
					fillRule: ShapePath.WindingFill

					PathSvg {
						path: iconItem.iconPath
					}
				}
			}

			Shape {
				id: fallbackShape

				anchors.centerIn: parent
				anchors.verticalCenterOffset: -1
				visible: iconItem.iconPath === ""
				preferredRendererType: Shape.CurveRenderer
				scale: Math.min(langText.implicitHeight / height, langText.implicitHeight / width)

				ShapePath {
					strokeColor: Colors.palette.m3tertiary
					strokeWidth: 16
					capStyle: ShapePath.RoundCap
					joinStyle: ShapePath.RoundJoin
					fillColor: "transparent"

					PathSvg {
						path: "M 40 64 L 112 128 L 40 192"
					}
				}

				ShapePath {
					strokeColor: Colors.palette.m3tertiary
					strokeWidth: 16
					capStyle: ShapePath.RoundCap
					fillColor: "transparent"

					PathSvg {
						path: "M 120 192 L 216 192"
					}
				}
			}
		}

		CustomText {
			id: langText

			Layout.alignment: Qt.AlignVCenter
			color: root.codeHeaderColor
			font.pointSize: Tokens.font.size.small
			text: CodeIcons.name(root.language)
			visible: root.language.length > 0
		}

		Item {
			Layout.fillWidth: true
		}

		IconButton {
			icon: root.copied ? "check" : "content_copy"
			inactiveColor: "transparent"
			inactiveOnColor: root.codeHeaderColor
			label.animate: true
			type: IconButton.Text

			onClicked: {
				Quickshell.clipboardText = root.code;
				root.copied = true;
				copyResetTimer.restart();
			}

			Timer {
				id: copyResetTimer

				interval: 1500

				onTriggered: root.copied = false
			}
		}
	}

	CustomClippingRect {
		id: codeRect

		anchors.left: parent.left
		anchors.top: headerRow.bottom
		anchors.right: parent.right
		anchors.bottom: parent.bottom
		anchors.topMargin: Tokens.padding.small
		radius: root.radius - anchors.margins
		anchors.margins: Tokens.padding.extraSmall
		implicitWidth: codeText.implicitWidth + codeFlick.anchors.margins * 2
		implicitHeight: codeText.implicitHeight + codeFlick.anchors.margins * 2
		color: CodeColors.active.bg

		CustomText {
			id: code
			anchors.top: parent.top
			anchors.bottom: parent.bottom
			x: implicitWidth * (0 - codeFlick.visibleArea.xPosition) + Tokens.padding.small
			anchors.margins: Tokens.padding.small
			text: root.highlightedHtml(root.code, root.codeSpans)
			textFormat: Text.RichText
			color: CodeColors.active.normal
			layer.enabled: true
			clip: false
			font.family: Config.appearance.font.family.mono
			font.pointSize: Tokens.font.size.small
		}

		Flickable {
			id: codeFlick

			anchors.fill: parent
			anchors.margins: Tokens.padding.small

			CustomScrollBar.horizontal: CustomScrollBar {
				flickable: codeFlick
				parent: codeFlick.parent
				anchors.bottom: parent.bottom
				anchors.left: parent.left
				anchors.right: parent.right
			}

			TextAreaBase.flickable: TextAreaBase {
				id: codeText

				color: CodeColors.active.normal
				layer.enabled: true
				leftInset: Tokens.padding.small
				clip: false
				font.family: Config.appearance.font.family.mono
				font.pointSize: Tokens.font.size.small
				textFormat: Text.RichText
				text: code.text
				readOnly: true
			}
		}
	}
}
