import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Services.Pam
import QtQuick
import qs.Config

Scope {
	id: root

	property string buffer
	readonly property alias fprint: fprint
	property string fprintState
	required property WlSessionLock lock
	property string lockMessage
	readonly property alias passwd: passwd
	property string state

	signal flashMsg

	function handleKey(event: KeyEvent): void {
		if (passwd.active || state === "max")
			return;

		if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
			passwd.start();
		} else if (event.key === Qt.Key_Backspace) {
			if (event.modifiers & Qt.ControlModifier) {
				buffer = "";
			} else {
				buffer = buffer.slice(0, -1);
			}
		} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
			// No illegal characters (you are insane if you use unicode in your password)
			buffer += event.text;
		}
	}

	PamContext {
		id: passwd

		config: "passwd"
		configDirectory: Quickshell.shellDir + "/assets/pam.d"

		onCompleted: res => {
			if (res === PamResult.Success)
				return root.lock.unlock();

			if (res === PamResult.Error)
				root.state = "error";
			else if (res === PamResult.MaxTries)
				root.state = "max";
			else if (res === PamResult.Failed)
				root.state = "fail";

			root.flashMsg();
			stateReset.restart();
		}
		onMessageChanged: {
			if (message.startsWith("The account is locked"))
				root.lockMessage = message;
			else if (root.lockMessage && message.endsWith(" left to unlock)"))
				root.lockMessage += "\n" + message;
		}
		onResponseRequiredChanged: {
			if (!responseRequired)
				return;

			respond(root.buffer);
			root.buffer = "";
		}
	}

	PamContext {
		id: fprint

		property bool available
		property int errorTries
		property int tries

		function checkAvail(): void {
			if (!available || !Config.lock.enableFprint || !root.lock.secure) {
				abort();
				return;
			}

			tries = 0;
			errorTries = 0;
			start();
		}

		config: "fprint"
		configDirectory: Quickshell.shellDir + "/assets/pam.d"

		onCompleted: res => {
			if (!available)
				return;

			if (res === PamResult.Success)
				return root.lock.unlock();

			if (res === PamResult.Error) {
				root.fprintState = "error";
				errorTries++;
				if (errorTries < 5) {
					abort();
					errorRetry.restart();
				}
			} else if (res === PamResult.MaxTries) {
				// Isn't actually the real max tries as pam only reports completed
				// when max tries is reached.
				tries++;
				if (tries < Config.lock.maxFprintTries) {
					// Restart if not actually real max tries
					root.fprintState = "fail";
					start();
				} else {
					root.fprintState = "max";
					abort();
				}
			}

			root.flashMsg();
			fprintStateReset.start();
		}
	}

	Process {
		id: availProc

		command: ["sh", "-c", "fprintd-list $USER"]

		onExited: code => {
			fprint.available = code === 0;
			fprint.checkAvail();
		}
	}

	Timer {
		id: errorRetry

		interval: 800

		onTriggered: fprint.start()
	}

	Timer {
		id: stateReset

		interval: 4000

		onTriggered: {
			if (root.state !== "max")
				root.state = "";
		}
	}

	Timer {
		id: fprintStateReset

		interval: 4000

		onTriggered: {
			root.fprintState = "";
			fprint.errorTries = 0;
		}
	}

	Connections {
		function onSecureChanged(): void {
			if (root.lock.secure) {
				availProc.running = true;
				root.buffer = "";
				root.state = "";
				root.fprintState = "";
				root.lockMessage = "";
			}
		}

		function onUnlock(): void {
			fprint.abort();
		}

		target: root.lock
	}

	Connections {
		function onEnableFprintChanged(): void {
			fprint.checkAvail();
		}

		target: Config.lock
	}
}
