import { n as __exportAll, r as __toESM } from "./chunk-B-1-B7_t.js";
import { n as Button_default } from "./button-B9CnnuTu.js";
import { t as Input_default } from "./input-CwDp-vE8.js";
import { t as FieldFrame_default } from "./field-frame-DhCOWcMP.js";
import { computed, createBlock, createElementVNode, createVNode, defineComponent, mergeModels, normalizeClass, onBeforeUnmount, openBlock, ref, toDisplayString, unref, useModel, useTemplateRef, vShow, withCtx, withDirectives } from "vue";
import { Camera, Loader2, StopCircle } from "@lucide/vue";
//#region src/components/compositions/elements/input/BarcodeScannerInput.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = ["data-element-name"];
var _hoisted_2 = { class: "flex items-center gap-2" };
var BarcodeScannerInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "BarcodeScannerInput",
	props: /* @__PURE__ */ mergeModels({
		modelValue: {},
		id: {},
		name: {},
		label: {},
		hideLabel: { type: Boolean },
		required: { type: Boolean },
		readonly: { type: Boolean },
		disabled: { type: Boolean },
		helpText: {},
		errors: { default: () => [] },
		scanFormats: { default: () => [
			"code_128_reader",
			"ean_reader",
			"ean_8_reader",
			"code_39_reader",
			"upc_reader"
		] },
		autoStart: {
			type: Boolean,
			default: false
		},
		facingMode: { default: "environment" }
	}, {
		"modelValue": {},
		"modelModifiers": {}
	}),
	emits: ["update:modelValue"],
	setup(__props, { expose: __expose }) {
		/**
		* Props-driven barcode scanner input.
		*
		* Wraps `@ericblade/quagga2` to provide a live camera-driven barcode
		* scanner. Renders the last decoded value in a read-only Input plus a
		* "Scan" button that toggles a `<video>` viewport.
		*
		* The quagga library and the `getUserMedia` API are lazy-imported when
		* scanning starts so the component is safe to render in environments
		* without a camera (e.g. jsdom).
		*
		*   <BarcodeScannerInput v-model="ticket" label="Scan ticket" />
		*/
		const props = __props;
		const model = useModel(__props, "modelValue");
		/** Loose Quagga handle — type-definitions ship with the library but we
		*  keep the references local rather than pulling its full type graph. */
		const videoContainerRef = useTemplateRef("videoContainerRef");
		const scanning = ref(false);
		const starting = ref(false);
		const scanError = ref("");
		let quagga = null;
		let mediaStream = null;
		let detectedHandler = null;
		const buttonLabel = computed(() => {
			if (starting.value) return "Starting…";
			return scanning.value ? "Stop scanning" : "Scan";
		});
		const allErrors = computed(() => {
			if (scanError.value) return [...props.errors, scanError.value];
			return props.errors;
		});
		function releaseStream() {
			if (mediaStream) {
				for (const track of mediaStream.getTracks()) try {
					track.stop();
				} catch {}
				mediaStream = null;
			}
		}
		async function stop() {
			if (quagga) try {
				if (detectedHandler) quagga.offDetected(detectedHandler);
				await quagga.stop();
			} catch {}
			detectedHandler = null;
			quagga = null;
			releaseStream();
			scanning.value = false;
			starting.value = false;
		}
		async function start() {
			if (scanning.value || starting.value) return;
			scanError.value = "";
			starting.value = true;
			try {
				const nav = typeof navigator !== "undefined" ? navigator : void 0;
				if (!nav?.mediaDevices?.getUserMedia) throw new Error("Camera access is not available in this environment.");
				mediaStream = await nav.mediaDevices.getUserMedia({
					video: { facingMode: props.facingMode },
					audio: false
				});
				quagga = (await import("./quagga.min-C4fp76wT.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
				const target = videoContainerRef.value;
				if (!target) throw new Error("Scanner viewport is not mounted.");
				await quagga.init({
					inputStream: {
						type: "LiveStream",
						target,
						constraints: { facingMode: props.facingMode }
					},
					decoder: { readers: [...props.scanFormats] },
					locate: true
				});
				detectedHandler = (result) => {
					const code = result?.codeResult?.code;
					if (typeof code === "string" && code.length > 0) {
						model.value = code;
						stop();
					}
				};
				quagga.onDetected(detectedHandler);
				quagga.start();
				scanning.value = true;
			} catch (e) {
				scanError.value = e instanceof Error ? e.message : "Failed to start scanner.";
				await stop();
			} finally {
				starting.value = false;
			}
		}
		async function toggleScan() {
			if (scanning.value) await stop();
			else await start();
		}
		/**
		* Test hook — lets tests simulate a successful Quagga detection
		* without spinning up a real camera. Not part of the public contract;
		* exposed only via `wrapper.vm` in unit tests.
		*/
		function __handleDetectedForTest(code) {
			if (code) model.value = code;
		}
		__expose({
			__handleDetectedForTest,
			start,
			stop
		});
		if (props.autoStart) start();
		onBeforeUnmount(() => {
			stop();
		});
		return (_ctx, _cache) => {
			return openBlock(), createBlock(unref(FieldFrame_default), {
				label: __props.label,
				required: __props.required,
				"hide-label": __props.hideLabel,
				for: __props.id,
				"help-text": __props.helpText,
				errors: allErrors.value
			}, {
				default: withCtx(() => [createElementVNode("div", {
					class: "flex flex-col gap-2",
					"data-element-name": __props.name ?? void 0
				}, [createElementVNode("div", _hoisted_2, [createVNode(unref(Input_default), {
					id: __props.id,
					"model-value": model.value ?? void 0,
					name: __props.name ?? void 0,
					type: "text",
					readonly: "",
					disabled: __props.disabled,
					required: __props.required,
					placeholder: "No code scanned yet",
					"data-element-name": __props.name ?? void 0,
					class: normalizeClass(__props.errors.length ? "border-destructive ring-destructive" : "")
				}, null, 8, [
					"id",
					"model-value",
					"name",
					"disabled",
					"required",
					"data-element-name",
					"class"
				]), createVNode(unref(Button_default), {
					type: "button",
					variant: scanning.value ? "destructive" : "default",
					disabled: __props.disabled || __props.readonly || starting.value,
					"data-testid": "barcode-scan-toggle",
					onClick: toggleScan
				}, {
					default: withCtx(() => [starting.value ? (openBlock(), createBlock(unref(Loader2), {
						key: 0,
						class: "size-4 animate-spin"
					})) : scanning.value ? (openBlock(), createBlock(unref(StopCircle), {
						key: 1,
						class: "size-4"
					})) : (openBlock(), createBlock(unref(Camera), {
						key: 2,
						class: "size-4"
					})), createElementVNode("span", null, toDisplayString(buttonLabel.value), 1)]),
					_: 1
				}, 8, ["variant", "disabled"])]), withDirectives(createElementVNode("div", {
					ref_key: "videoContainerRef",
					ref: videoContainerRef,
					"data-testid": "barcode-scan-viewport",
					class: "relative h-64 w-full overflow-hidden rounded-md border border-input bg-muted"
				}, null, 512), [[vShow, scanning.value || starting.value]])], 8, _hoisted_1)]),
				_: 1
			}, 8, [
				"label",
				"required",
				"hide-label",
				"for",
				"help-text",
				"errors"
			]);
		};
	}
});
//#endregion
//#region src/components/compositions/elements/input/BarcodeScannerInput.vue
var BarcodeScannerInput_exports = /* @__PURE__ */ __exportAll({ default: () => BarcodeScannerInput_default });
var BarcodeScannerInput_default = BarcodeScannerInput_vue_vue_type_script_setup_true_lang_default;
//#endregion
export { BarcodeScannerInput_exports as n, BarcodeScannerInput_default as t };

//# sourceMappingURL=BarcodeScannerInput-DSG8LwGv.js.map