"use client";
import { useCallback, useEffect, useRef, useState } from "react";
//#region packages/@mantine/hooks/src/use-mask/use-mask.ts
const DEFAULT_TOKENS = {
	"9": /[0-9]/,
	a: /[A-Za-z]/,
	A: /[A-Z]/,
	"*": /[A-Za-z0-9]/,
	"#": /[-+0-9]/
};
const MAX_UNDO_HISTORY = 100;
function parseMask(mask, tokens) {
	if (Array.isArray(mask)) return mask.map((item) => {
		if (item instanceof RegExp) return {
			type: "token",
			char: "_",
			pattern: item
		};
		return {
			type: "literal",
			char: item
		};
	});
	const slots = [];
	let optional = false;
	for (let i = 0; i < mask.length; i++) {
		const char = mask[i];
		if (char === "\\" && i + 1 < mask.length) {
			i++;
			slots.push({
				type: "literal",
				char: mask[i]
			});
			continue;
		}
		if (char === "?") {
			optional = true;
			continue;
		}
		if (tokens[char]) slots.push({
			type: "token",
			char,
			pattern: tokens[char],
			optional
		});
		else slots.push({
			type: "literal",
			char,
			optional
		});
	}
	return slots;
}
function getSlotChar(slotCharOption, index) {
	if (slotCharOption === null || slotCharOption === "" || slotCharOption === void 0) return "";
	if (slotCharOption.length > 1) return slotCharOption[index] || "_";
	return slotCharOption;
}
function applyMaskToRaw(raw, slots, _slotCharOption, transform) {
	let result = "";
	let rawIndex = 0;
	let slotIndex = 0;
	for (slotIndex = 0; slotIndex < slots.length; slotIndex++) {
		const slot = slots[slotIndex];
		if (slot.type === "literal") result += slot.char;
		else if (rawIndex < raw.length) {
			const ch = transform ? transform(raw[rawIndex]) : raw[rawIndex];
			if (slot.pattern && slot.pattern.test(ch)) {
				result += ch;
				rawIndex++;
			} else {
				rawIndex++;
				slotIndex--;
			}
		} else break;
	}
	return result;
}
function buildDisplayValue(value, slots, slotCharOption, showSlots) {
	if (!showSlots) return value;
	let display = value;
	for (let i = value.length; i < slots.length; i++) {
		const slot = slots[i];
		if (slot.type === "literal") display += slot.char;
		else {
			const sc = getSlotChar(slotCharOption, i);
			if (!sc) break;
			display += sc;
		}
	}
	return display;
}
function extractRaw(masked, slots) {
	let raw = "";
	for (let i = 0; i < masked.length && i < slots.length; i++) if (slots[i].type === "token") raw += masked[i];
	return raw;
}
function checkComplete(masked, slots) {
	for (let i = 0; i < slots.length; i++) if (slots[i].type === "token" && !slots[i].optional) {
		if (i >= masked.length) return false;
		if (!slots[i].pattern.test(masked[i])) return false;
	}
	return true;
}
function findNextTokenIndex(slots, from) {
	for (let i = from; i < slots.length; i++) if (slots[i].type === "token") return i;
	return slots.length;
}
function findPrevTokenIndex(slots, from) {
	for (let i = from; i >= 0; i--) if (slots[i].type === "token") return i;
	return -1;
}
function processInput(inputValue, slots, _slotCharOption) {
	let result = "";
	let inputIndex = 0;
	for (let slotIndex = 0; slotIndex < slots.length && inputIndex <= inputValue.length; slotIndex++) {
		const slot = slots[slotIndex];
		if (slot.type === "literal") {
			result += slot.char;
			if (inputIndex < inputValue.length && inputValue[inputIndex] === slot.char) inputIndex++;
			continue;
		}
		if (inputIndex >= inputValue.length) break;
		while (inputIndex < inputValue.length) {
			const ch = inputValue[inputIndex];
			inputIndex++;
			if (slot.pattern.test(ch)) {
				result += ch;
				break;
			}
		}
		if (result.length <= slotIndex) break;
	}
	return result;
}
function getResolvedOptions(options, rawValue) {
	const tokens = {
		...DEFAULT_TOKENS,
		...options.tokens
	};
	let mask = options.mask;
	let slotChar = options.slotChar === void 0 ? "_" : options.slotChar;
	let separate = options.separate ?? false;
	if (options.modify) {
		const overrides = options.modify(rawValue);
		if (overrides) {
			if (overrides.mask !== void 0) mask = overrides.mask;
			if (overrides.tokens !== void 0) Object.assign(tokens, overrides.tokens);
			if (overrides.slotChar !== void 0) slotChar = overrides.slotChar;
			if (overrides.separate !== void 0) separate = overrides.separate;
		}
	}
	return {
		slots: parseMask(mask, tokens),
		slotChar,
		separate,
		tokens,
		transform: options.transform
	};
}
function formatMask(raw, options) {
	const { slots, slotChar, transform } = getResolvedOptions(options, raw);
	return applyMaskToRaw(raw, slots, slotChar, transform);
}
function unformatMask(masked, options) {
	const { slots } = getResolvedOptions(options, "");
	return extractRaw(masked, slots);
}
function isMaskComplete(masked, options) {
	const { slots } = getResolvedOptions(options, "");
	return checkComplete(masked, slots);
}
function generatePattern(mode, options) {
	const { slots } = getResolvedOptions(options, "");
	let pattern = "";
	for (const slot of slots) if (slot.type === "literal") pattern += slot.char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
	else {
		const src = slot.pattern.source;
		if (mode === "full-inexact") pattern += slot.optional ? `${src}?` : src;
		else pattern += slot.optional ? `(${src})?` : `(${src})`;
	}
	return pattern;
}
function useMask(options) {
	const optionsRef = useRef(options);
	optionsRef.current = options;
	const inputRef = useRef(null);
	const [maskedValue, setMaskedValue] = useState("");
	const [rawValue, setRawValue] = useState("");
	const processedRef = useRef("");
	const displayValueRef = useRef("");
	const rawValueRef = useRef("");
	const wasCompleteRef = useRef(false);
	const isFocusedRef = useRef(false);
	const undoStackRef = useRef([]);
	const redoStackRef = useRef([]);
	const getOptions = useCallback(() => {
		const opts = optionsRef.current;
		return getResolvedOptions(opts, rawValue);
	}, [rawValue]);
	const updateValue = useCallback((newMasked, cursorPos) => {
		const opts = optionsRef.current;
		const { slots } = getResolvedOptions(opts, extractRaw(newMasked, getResolvedOptions(opts, "").slots));
		const { slots: resolvedSlots, slotChar } = getResolvedOptions(opts, extractRaw(newMasked, slots));
		const reprocessed = processInput(newMasked, resolvedSlots, slotChar);
		const newRaw = extractRaw(reprocessed, resolvedSlots);
		const showSlots = opts.alwaysShowMask || isFocusedRef.current;
		const showOnFocus = opts.showMaskOnFocus !== false;
		const displayValue = buildDisplayValue(reprocessed, resolvedSlots, slotChar, showSlots && (showOnFocus || reprocessed.length > 0));
		processedRef.current = reprocessed;
		displayValueRef.current = displayValue;
		rawValueRef.current = newRaw;
		setMaskedValue(displayValue);
		setRawValue(newRaw);
		if (inputRef.current) {
			inputRef.current.value = displayValue;
			if (cursorPos !== void 0 && document.activeElement === inputRef.current) {
				const pos = Math.min(cursorPos, reprocessed.length);
				inputRef.current.setSelectionRange(pos, pos);
			}
		}
		if (opts.onChangeRaw) opts.onChangeRaw(newRaw, displayValue);
		const complete = checkComplete(reprocessed, resolvedSlots);
		if (complete && !wasCompleteRef.current && opts.onComplete) opts.onComplete(displayValue, newRaw);
		wasCompleteRef.current = complete;
		return {
			displayValue,
			newRaw,
			reprocessed,
			resolvedSlots
		};
	}, [getOptions]);
	const pushUndoState = useCallback(() => {
		const selectionStart = inputRef.current?.selectionStart ?? rawValueRef.current.length;
		const state = {
			rawValue: rawValueRef.current,
			selectionStart
		};
		const stack = undoStackRef.current;
		const top = stack[stack.length - 1];
		if (top && top.rawValue === state.rawValue && top.selectionStart === state.selectionStart) return;
		stack.push(state);
		if (stack.length > MAX_UNDO_HISTORY) stack.shift();
		redoStackRef.current = [];
	}, []);
	const applyHistoryState = useCallback((target) => {
		const opts = optionsRef.current;
		const { slots, slotChar, transform } = getResolvedOptions(opts, target.rawValue);
		updateValue(applyMaskToRaw(target.rawValue, slots, slotChar, transform), target.selectionStart);
	}, [updateValue]);
	const handleInput = useCallback((e) => {
		const input = e.target;
		const opts = optionsRef.current;
		const { slots: resolvedSlots, slotChar, transform } = getResolvedOptions(opts, "");
		const prev = displayValueRef.current;
		const curr = input.value;
		let prefixLen = 0;
		const maxPrefix = Math.min(prev.length, curr.length);
		while (prefixLen < maxPrefix && prev[prefixLen] === curr[prefixLen]) prefixLen++;
		let suffixLen = 0;
		const maxSuffix = Math.min(prev.length - prefixLen, curr.length - prefixLen);
		while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === curr[curr.length - 1 - suffixLen]) suffixLen++;
		const insertedText = curr.slice(prefixLen, curr.length - suffixLen);
		const removedEnd = prev.length - suffixLen;
		const beforeRaw = extractRaw(prev.slice(0, prefixLen), resolvedSlots.slice(0, prefixLen));
		const afterRaw = extractRaw(prev.slice(removedEnd), resolvedSlots.slice(removedEnd));
		const reformatted = applyMaskToRaw(beforeRaw + insertedText + afterRaw, resolvedSlots, slotChar, transform);
		const maskedPrefix = applyMaskToRaw(beforeRaw + insertedText, resolvedSlots, slotChar, transform);
		if (reformatted !== prev) pushUndoState();
		updateValue(reformatted, maskedPrefix.length);
	}, [pushUndoState, updateValue]);
	const clampCursorToProcessed = useCallback((input) => {
		const start = input.selectionStart ?? 0;
		if (start !== (input.selectionEnd ?? 0)) return;
		const opts = optionsRef.current;
		const { slots } = getResolvedOptions(opts, "");
		const processed = processedRef.current;
		const endPos = processed.length > 0 ? findNextEditablePosition(processed.length, slots, processed) : findNextTokenIndex(slots, 0);
		const startPos = findNextTokenIndex(slots, 0);
		if (start > endPos || start < startPos) input.setSelectionRange(endPos, endPos);
	}, []);
	const handleFocus = useCallback(() => {
		isFocusedRef.current = true;
		const opts = optionsRef.current;
		const input = inputRef.current;
		if (!input) return;
		const { slots, slotChar } = getResolvedOptions(opts, "");
		const showOnFocus = opts.showMaskOnFocus !== false;
		const processed = processedRef.current;
		if (showOnFocus || opts.alwaysShowMask) {
			const display = buildDisplayValue(processed, slots, slotChar, true);
			input.value = display;
			displayValueRef.current = display;
			setMaskedValue(display);
		}
		requestAnimationFrame(() => {
			if (input === document.activeElement) clampCursorToProcessed(input);
		});
	}, [clampCursorToProcessed]);
	const handleMouseUp = useCallback(() => {
		const input = inputRef.current;
		if (!input || input !== document.activeElement) return;
		clampCursorToProcessed(input);
	}, [clampCursorToProcessed]);
	const handleMouseDown = useCallback(() => {
		const input = inputRef.current;
		if (!input) return;
		requestAnimationFrame(() => {
			if (input !== document.activeElement) return;
			const start = input.selectionStart ?? 0;
			if (start !== (input.selectionEnd ?? 0)) return;
			const opts = optionsRef.current;
			const { slots } = getResolvedOptions(opts, "");
			const processed = processedRef.current;
			const endPos = processed.length > 0 ? findNextEditablePosition(processed.length, slots, processed) : findNextTokenIndex(slots, 0);
			if (start > endPos) input.setSelectionRange(endPos, endPos);
		});
	}, []);
	const handleBlur = useCallback(() => {
		isFocusedRef.current = false;
		const opts = optionsRef.current;
		const input = inputRef.current;
		if (!input) return;
		const { slots, slotChar } = getResolvedOptions(opts, rawValue);
		const expectedFocusDisplay = buildDisplayValue(processedRef.current, slots, slotChar, true);
		const processed = input.value === expectedFocusDisplay ? processedRef.current : processInput(input.value, slots, slotChar);
		const complete = checkComplete(processed, slots);
		if (opts.autoClear && !complete && processed.length > 0) {
			input.value = "";
			processedRef.current = "";
			displayValueRef.current = "";
			rawValueRef.current = "";
			setMaskedValue("");
			setRawValue("");
			wasCompleteRef.current = false;
			if (opts.onChangeRaw) opts.onChangeRaw("", "");
			if (opts.alwaysShowMask) {
				const emptyDisplay = buildDisplayValue("", slots, slotChar, true);
				input.value = emptyDisplay;
				displayValueRef.current = emptyDisplay;
				setMaskedValue(emptyDisplay);
			}
			return;
		}
		if (!opts.alwaysShowMask && !complete) {
			if (extractRaw(processed, slots).length === 0) {
				input.value = "";
				processedRef.current = "";
				displayValueRef.current = "";
				rawValueRef.current = "";
				setMaskedValue("");
				setRawValue("");
				wasCompleteRef.current = false;
				if (opts.onChangeRaw) opts.onChangeRaw("", "");
				return;
			}
			const display = buildDisplayValue(processed, slots, slotChar, false);
			input.value = display;
			displayValueRef.current = display;
			setMaskedValue(display);
		}
	}, [rawValue]);
	const handleKeyDown = useCallback((e) => {
		const input = e.target;
		const opts = optionsRef.current;
		const { slots, slotChar, transform } = getResolvedOptions(opts, rawValue);
		const start = input.selectionStart ?? 0;
		const end = input.selectionEnd ?? 0;
		const processed = processedRef.current;
		const modifier = e.metaKey || e.ctrlKey && !e.altKey;
		const key = e.key.toLowerCase();
		if (modifier && key === "z" && !e.shiftKey) {
			e.preventDefault();
			const prev = undoStackRef.current.pop();
			if (!prev) return;
			redoStackRef.current.push({
				rawValue: rawValueRef.current,
				selectionStart: input.selectionStart ?? 0
			});
			applyHistoryState(prev);
			return;
		}
		if (modifier && (key === "z" && e.shiftKey || key === "y" && !e.shiftKey)) {
			e.preventDefault();
			const next = redoStackRef.current.pop();
			if (!next) return;
			undoStackRef.current.push({
				rawValue: rawValueRef.current,
				selectionStart: input.selectionStart ?? 0
			});
			applyHistoryState(next);
			return;
		}
		if (e.key === "Backspace") {
			e.preventDefault();
			if (e.metaKey || e.ctrlKey && !e.altKey) {
				const clampedStart = Math.min(start, processed.length);
				const newValue = applyMaskToRaw(extractRaw(processed.slice(clampedStart), slots.slice(clampedStart)), slots, slotChar, transform);
				pushUndoState();
				updateValue(newValue, 0);
				return;
			}
			if (start !== end) {
				const clampedEnd = Math.min(end, processed.length);
				const before = processed.slice(0, start);
				const afterRaw = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
				const newValue = applyMaskToRaw(extractRaw(before, slots) + afterRaw, slots, slotChar, transform);
				pushUndoState();
				updateValue(newValue, start);
				return;
			}
			if (start === 0) return;
			let deletePos = start - 1;
			while (deletePos >= 0 && slots[deletePos] && slots[deletePos].type === "literal") deletePos--;
			if (deletePos < 0) return;
			const newValue = applyMaskToRaw(extractRaw(processed.slice(0, deletePos), slots.slice(0, deletePos)) + extractRaw(processed.slice(deletePos + 1), slots.slice(deletePos + 1)), slots, slotChar, transform);
			pushUndoState();
			updateValue(newValue, deletePos);
		} else if (e.key === "Delete") {
			e.preventDefault();
			if (start !== end) {
				const clampedEnd = Math.min(end, processed.length);
				const before = processed.slice(0, start);
				const afterRaw = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
				const newValue = applyMaskToRaw(extractRaw(before, slots) + afterRaw, slots, slotChar, transform);
				pushUndoState();
				updateValue(newValue, start);
				return;
			}
			let deletePos = start;
			while (deletePos < slots.length && slots[deletePos] && slots[deletePos].type === "literal") deletePos++;
			if (deletePos >= processed.length) return;
			const newValue = applyMaskToRaw(extractRaw(processed.slice(0, start), slots.slice(0, start)) + extractRaw(processed.slice(deletePos + 1), slots.slice(deletePos + 1)), slots, slotChar, transform);
			pushUndoState();
			updateValue(newValue, start);
		} else if (e.key === "ArrowRight" && !e.shiftKey) {
			const nextPos = findNextEditablePosition(start + 1, slots, input.value);
			if (nextPos !== start + 1) {
				e.preventDefault();
				input.setSelectionRange(nextPos, nextPos);
			}
		} else if (e.key === "ArrowLeft" && !e.shiftKey) {
			if (start > 0) {
				const prevToken = findPrevTokenIndex(slots, start - 1);
				if (prevToken >= 0 && prevToken !== start - 1) {
					e.preventDefault();
					input.setSelectionRange(prevToken + 1, prevToken + 1);
				}
			}
		} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
			e.preventDefault();
			let insertPos = Math.min(start, processed.length);
			while (insertPos < slots.length && slots[insertPos] && slots[insertPos].type === "literal") insertPos++;
			if (insertPos >= slots.length) return;
			const slot = slots[insertPos];
			const ch = transform ? transform(e.key) : e.key;
			if (!slot.pattern.test(ch)) return;
			const beforeRaw = extractRaw(processed.slice(0, insertPos), slots.slice(0, insertPos));
			const afterRaw = start < end ? extractRaw(processed.slice(Math.min(end, processed.length)), slots.slice(Math.min(end, processed.length))) : extractRaw(processed.slice(insertPos), slots.slice(insertPos));
			const newValue = applyMaskToRaw(beforeRaw + ch + afterRaw, slots, slotChar, transform);
			const newCursorPos = findNextEditablePosition(insertPos + 1, slots, newValue);
			pushUndoState();
			updateValue(newValue, newCursorPos);
		}
	}, [
		applyHistoryState,
		pushUndoState,
		rawValue,
		updateValue
	]);
	const handlePaste = useCallback((e) => {
		e.preventDefault();
		const input = e.target;
		const opts = optionsRef.current;
		const pastedText = e.clipboardData?.getData("text") ?? "";
		const start = input.selectionStart ?? 0;
		const end = input.selectionEnd ?? 0;
		const processed = processedRef.current;
		const { slots, slotChar, transform } = getResolvedOptions(opts, "");
		const clampedStart = Math.min(start, processed.length);
		const clampedEnd = Math.min(end, processed.length);
		const beforeRaw = extractRaw(processed.slice(0, clampedStart), slots.slice(0, clampedStart));
		const afterRaw = extractRaw(processed.slice(clampedEnd), slots.slice(clampedEnd));
		const newValue = applyMaskToRaw(beforeRaw + pastedText + afterRaw, slots, slotChar, transform);
		pushUndoState();
		updateValue(newValue);
		const maskedPrefix = applyMaskToRaw(beforeRaw + pastedText, slots, slotChar, transform);
		const pasteEndPos = Math.min(maskedPrefix.length, slots.length);
		if (input === document.activeElement) input.setSelectionRange(pasteEndPos, pasteEndPos);
	}, [pushUndoState, updateValue]);
	const setAriaAttributes = useCallback((input) => {
		if (optionsRef.current.invalid) input.setAttribute("aria-invalid", "true");
		else input.removeAttribute("aria-invalid");
	}, []);
	const refCallback = useCallback((node) => {
		const prevInput = inputRef.current;
		if (prevInput) {
			prevInput.removeEventListener("input", handleInput);
			prevInput.removeEventListener("focus", handleFocus);
			prevInput.removeEventListener("blur", handleBlur);
			prevInput.removeEventListener("mousedown", handleMouseDown);
			prevInput.removeEventListener("mouseup", handleMouseUp);
			prevInput.removeEventListener("keydown", handleKeyDown);
			prevInput.removeEventListener("paste", handlePaste);
		}
		inputRef.current = node;
		if (node) {
			node.addEventListener("input", handleInput);
			node.addEventListener("focus", handleFocus);
			node.addEventListener("blur", handleBlur);
			node.addEventListener("mousedown", handleMouseDown);
			node.addEventListener("mouseup", handleMouseUp);
			node.addEventListener("keydown", handleKeyDown);
			node.addEventListener("paste", handlePaste);
			setAriaAttributes(node);
			if (options.alwaysShowMask && !node.value) {
				const { slots, slotChar } = getResolvedOptions(options, "");
				const display = buildDisplayValue("", slots, slotChar, true);
				node.value = display;
				displayValueRef.current = display;
				setMaskedValue(display);
			}
		}
	}, [
		handleInput,
		handleFocus,
		handleBlur,
		handleMouseDown,
		handleMouseUp,
		handleKeyDown,
		handlePaste,
		setAriaAttributes,
		options
	]);
	useEffect(() => {
		const input = inputRef.current;
		if (!input) return;
		setAriaAttributes(input);
	}, [options.invalid, setAriaAttributes]);
	return {
		ref: refCallback,
		value: maskedValue,
		rawValue,
		isComplete: (() => {
			const { slots } = getOptions();
			return checkComplete(processedRef.current, slots);
		})(),
		reset: useCallback(() => {
			const opts = optionsRef.current;
			const input = inputRef.current;
			processedRef.current = "";
			displayValueRef.current = "";
			rawValueRef.current = "";
			undoStackRef.current = [];
			redoStackRef.current = [];
			setMaskedValue("");
			setRawValue("");
			wasCompleteRef.current = false;
			if (input) if (opts.alwaysShowMask) {
				const { slots, slotChar } = getResolvedOptions(opts, "");
				const display = buildDisplayValue("", slots, slotChar, true);
				input.value = display;
				displayValueRef.current = display;
				setMaskedValue(display);
			} else input.value = "";
			if (opts.onChangeRaw) opts.onChangeRaw("", "");
		}, [])
	};
}
function findNextEditablePosition(from, slots, value) {
	let pos = from;
	while (pos < slots.length && pos < value.length && slots[pos] && slots[pos].type === "literal") pos++;
	return pos;
}
//#endregion
export { formatMask, generatePattern, isMaskComplete, unformatMask, useMask };

//# sourceMappingURL=use-mask.mjs.map