// Tailwind-styled datetime picker (vanilla JS). Enhance any input with class .ts-datepicker
(function () {
	class TailwindDateTimePicker {
		constructor(input) {
			this.input = input;
			this.type = this.input.dataset.type || "date"; // date | time | datetime | month
			if (this.input.dataset.format) {
				this.format = this.normalizeFormat(this.input.dataset.format);
			} else if (this.type === "time") {
				this.format = "H:i";
			} else if (this.type === "month") {
				this.format = "Y-m";
			} else {
				this.format = "Y-m-d";
			}
			this.is24h = this.input.dataset.time24 !== "false";
			this.showTodayBtn = this.input.dataset.todayBtn === "true";
			const autoCloseAttr = this.input.dataset.autoClose;
			if (autoCloseAttr === undefined) {
				this.autoClose = this.type === "date";
			} else {
				this.autoClose = autoCloseAttr !== "false" && autoCloseAttr !== "0";
			}
			this.minDate = this.input.dataset.minDate
				? this.parseDateValue(this.input.dataset.minDate)
				: null;
			this.maxDate = this.input.dataset.maxDate
				? this.parseDateValue(this.input.dataset.maxDate)
				: null;
			this.minLink = this.input.dataset.min || null;
			this.maxLink = this.input.dataset.max || null;
			this.selectedDate = null;
			this.viewMode = this.type === "month" ? "months" : "days"; // days | months | years
			this.viewDate = new Date();
			this.isOpen = false;
			this.init();
		}

		normalizeFormat(format) {
			return String(format || "Y-m-d")
				.replace(/yyyy/g, "Y")
				.replace(/yy/g, "y")
				.replace(/dd/g, "d")
				.replace(/mm/g, "m");
		}

		init() {
			const wrap = document.createElement("div");
			wrap.className = "relative";
			this.input.parentNode.insertBefore(wrap, this.input);
			wrap.appendChild(this.input);
			this.container = wrap;
			if (!this.input.getAttribute("autocomplete")) {
				this.input.setAttribute("autocomplete", "off");
			}
			this.input.dataset.enhanced = "true";
			this.input._twDatepicker = this;

			const icon = document.createElement("span");
			icon.className =
				"absolute right-3 top-3 flex items-center text-slate-400 pointer-events-none";
			icon.innerHTML =
				this.type === "time"
					? '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>'
					: '<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10m-11 8h12a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>';
			wrap.appendChild(icon);

			this.picker = document.createElement("div");
			this.picker.className =
				"absolute z-50 mt-1 bg-white dark:bg-slate-800 rounded-lg shadow-xl border border-slate-200 dark:border-slate-700 hidden p-4 " +
				(this.type === "datetime" ? "w-[22rem]" : "w-72") +
				" origin-top transform transition duration-150";
			this.picker.dataset.datepickerPanel = "true";
			document.body.appendChild(this.picker);

			this.input.addEventListener("focus", () => this.open());
			this.input.addEventListener("click", (e) => {
				e.stopPropagation();
				this.open();
			});
			this.input.addEventListener("change", (e) =>
				this.parseInput(e.target.value)
			);
			document.addEventListener("click", (e) => {
				if (!wrap.contains(e.target) && !this.picker.contains(e.target)) this.close();
			});
			this.picker.addEventListener("click", (e) => e.stopPropagation());

			// Linked min/max selectors (data-min / data-max)
			if (this.minLink) this.bindLinkedConstraint("min", this.minLink);
			if (this.maxLink) this.bindLinkedConstraint("max", this.maxLink);
		}

		bindLinkedConstraint(type, selector) {
			if (!selector) return;
			const target = document.querySelector(selector);
			if (!target) return;
			const update = () => {
				const val = target.value;
				if (!val) {
					if (type === "min") this.setMinDate(null);
					if (type === "max") this.setMaxDate(null);
					return;
				}
				if (type === "min") this.setMinDate(val);
				if (type === "max") this.setMaxDate(val);
			};
			target.addEventListener("change", update);
			update();
		}

		setMinDate(date) {
			this.minDate = date ? this.parseDateValue(date) : null;
			if (this.isOpen) this.render();
		}
		setMaxDate(date) {
			this.maxDate = date ? this.parseDateValue(date) : null;
			if (this.isOpen) this.render();
		}

		parseDateValue(value) {
			if (!value) return null;
			if (value instanceof Date) {
				return isNaN(value.getTime()) ? null : new Date(value);
			}
			const stringValue = String(value).trim();
			const formattedDate = this.parseFormattedDateValue(stringValue);
			if (formattedDate) return formattedDate;
			const dateTimeMatch = stringValue.match(
				/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{1,2}))?$/
			);
			if (dateTimeMatch) {
				const parsed = new Date(
					parseInt(dateTimeMatch[1], 10),
					parseInt(dateTimeMatch[2], 10) - 1,
					parseInt(dateTimeMatch[3], 10),
					parseInt(dateTimeMatch[4] || "0", 10),
					parseInt(dateTimeMatch[5] || "0", 10),
					0,
					0
				);
				return isNaN(parsed.getTime()) ? null : parsed;
			}
			const monthMatch = stringValue.match(/^(\d{4})-(\d{1,2})$/);
			if (monthMatch) {
				const parsed = new Date(
					parseInt(monthMatch[1], 10),
					parseInt(monthMatch[2], 10) - 1,
					1
				);
				return isNaN(parsed.getTime()) ? null : parsed;
			}
			const timeMatch = stringValue.match(/^(\d{1,2}):(\d{1,2})/);
			if (timeMatch) {
				const parsed = new Date();
				parsed.setHours(
					parseInt(timeMatch[1], 10),
					parseInt(timeMatch[2], 10),
					0,
					0
				);
				return isNaN(parsed.getTime()) ? null : parsed;
			}
			const parsed = new Date(stringValue);
			return isNaN(parsed.getTime()) ? null : parsed;
		}

		parseFormattedDateValue(value) {
			if (!value || this.type === "time") return null;
			const parts = String(value).trim().match(/^(\d{1,4})[.\-/](\d{1,2})[.\-/](\d{1,4})$/);
			if (!parts) return null;

			const tokens = this.format.match(/[Yymd]/g);
			if (!tokens || tokens.length < 3) return null;

			const parsed = {};
			tokens.slice(0, 3).forEach((token, index) => {
				parsed[token] = parseInt(parts[index + 1], 10);
			});

			let year = parsed.Y || parsed.y;
			if (year && year < 100) year += year >= 70 ? 1900 : 2000;
			const month = parsed.m;
			const day = parsed.d;
			if (!year || !month || !day) return null;

			const date = new Date(year, month - 1, day, 0, 0, 0, 0);
			if (
				date.getFullYear() !== year ||
				date.getMonth() !== month - 1 ||
				date.getDate() !== day
			) {
				return null;
			}
			return date;
		}

		formatDate(date) {
			if (!date) return "";
			let str = this.format;
			const day = date.getDate(),
				month = date.getMonth(),
				year = date.getFullYear();
			const hours = date.getHours(),
				minutes = date.getMinutes();
			const ampm = hours >= 12 ? "PM" : "AM";
			const hours12 = hours % 12 || 12;
			const pad = (n) => n.toString().padStart(2, "0");
			const monthsShort = [
				"Jan",
				"Feb",
				"Mar",
				"Apr",
				"May",
				"Jun",
				"Jul",
				"Aug",
				"Sep",
				"Oct",
				"Nov",
				"Dec",
			];
			const repl = {
				Y: year,
				y: year.toString().slice(-2),
				m: pad(month + 1),
				M: monthsShort[month],
				d: pad(day),
				j: day,
				H: pad(hours),
				h: pad(hours12),
				i: pad(minutes),
				K: ampm,
			};
			Object.entries(repl).forEach(([k, v]) => {
				str = str.replace(k, v);
			});
			return str;
		}

		parseInput(val) {
			if (!val) {
				this.selectedDate = null;
				this.render();
				return;
			}
			const d = this.parseDateValue(val);
			if (d) {
				this.selectedDate = d;
				this.viewDate = new Date(d);
				this.render();
			}
		}

		open() {
			if (this.isOpen) return;
			this.isOpen = true;
			const modalBody = this.input.closest("[data-modal-body]");
			if (modalBody) {
				this.modalBodyEl = modalBody;
				this.modalBodyOverflow = modalBody.style.overflow;
				this.modalBodyOverflowX = modalBody.style.overflowX;
				this.modalBodyOverflowY = modalBody.style.overflowY;
				modalBody.style.overflowX = "visible";
				if (!modalBody.style.overflowY) {
					modalBody.style.overflowY = "auto";
				}
			}
			this.picker.style.display = "";
			this.picker.classList.remove("hidden");
			requestAnimationFrame(() => {
				this.picker.classList.remove("opacity-0", "scale-95");
				this.picker.classList.add("opacity-100", "scale-100");
				this.positionPicker();
			});
			if (!this.selectedDate && this.input.value)
				this.parseInput(this.input.value);
			if (!this.selectedDate) {
				this.selectedDate = new Date();
				if (this.minDate && this.selectedDate < this.minDate)
					this.selectedDate = new Date(this.minDate);
				if (this.maxDate && this.selectedDate > this.maxDate)
					this.selectedDate = new Date(this.maxDate);
			}
			this.viewDate = new Date(this.selectedDate);
			this.viewMode = this.type === "month" ? "months" : "days";
			this.render();
			this.bindPositionListeners();
			this.positionPicker();
		}

		close(force = false) {
			if (!this.isOpen && !force) return;
			this.isOpen = false;
			this.picker.classList.add("hidden");
			this.picker.style.display = "none";
			this.picker.classList.remove("opacity-100", "scale-100");
			// this.picker.classList.add("opacity-0", "scale-95");
			this.input.blur();
			if (this.modalBodyEl) {
				this.modalBodyEl.style.overflow = this.modalBodyOverflow || "";
				this.modalBodyEl.style.overflowX = this.modalBodyOverflowX || "";
				this.modalBodyEl.style.overflowY = this.modalBodyOverflowY || "";
				this.modalBodyEl = null;
				this.modalBodyOverflow = null;
				this.modalBodyOverflowX = null;
				this.modalBodyOverflowY = null;
			}
			this.unbindPositionListeners();
			this.resetPickerPosition();
			this.input.dispatchEvent(new Event("change"));
		}

		selectDate(date, close = true) {
			const minTime = this.minDate ? new Date(this.minDate).setHours(0, 0, 0, 0) : null;
			const maxTime = this.maxDate ? new Date(this.maxDate).setHours(23, 59, 59, 999) : null;
			if (minTime && date < minTime) return;
			if (maxTime && date > maxTime) return;
			if (this.type === "datetime" && !this.selectedDate) {
				const now = new Date();
				date.setHours(now.getHours());
				date.setMinutes(now.getMinutes());
			} else if (this.type === "datetime" && this.selectedDate) {
				date.setHours(this.selectedDate.getHours());
				date.setMinutes(this.selectedDate.getMinutes());
			}
			this.selectedDate = date;
			this.updateInput();
			if (close && this.autoClose && this.type !== "datetime") this.close();
			else this.render();
		}

		updateInput() {
			this.input.value = this.selectedDate
				? this.formatDate(this.selectedDate)
				: "";
		}

		render() {
			this.picker.innerHTML = "";
			if (this.type !== "time") this.renderCalendar();
			if (this.type === "datetime") {
				const divider = document.createElement("div");
				divider.className = "my-3 border-t border-slate-200 dark:border-slate-700";
				this.picker.appendChild(divider);
			}
			if (this.type === "time" || this.type === "datetime") this.renderTime();
			if (this.showTodayBtn && this.type !== "time" && this.type !== "datetime") this.renderQuickActions();
			if (this.isOpen) this.positionPicker();
		}

		bindPositionListeners() {
			if (this._positionBound) return;
			this._positionBound = () => this.positionPicker();
			window.addEventListener("resize", this._positionBound);
			window.addEventListener("scroll", this._positionBound, true);
		}

		unbindPositionListeners() {
			if (!this._positionBound) return;
			window.removeEventListener("resize", this._positionBound);
			window.removeEventListener("scroll", this._positionBound, true);
			this._positionBound = null;
		}

		resetPickerPosition() {
			this.picker.style.position = "";
			this.picker.style.top = "";
			this.picker.style.left = "";
			this.picker.style.right = "";
			this.picker.style.bottom = "";
			this.picker.style.minWidth = "";
			this.picker.style.width = "";
			this.picker.style.maxWidth = "";
			this.picker.style.zIndex = "";
			this.picker.classList.remove("origin-bottom");
			if (!this.picker.classList.contains("origin-top")) {
				this.picker.classList.add("origin-top");
			}
		}

		positionPicker() {
			if (!this.isOpen) return;
			if (this.picker.classList.contains("hidden")) return;
			this.picker.style.minWidth = "";
			this.picker.style.width = "";
			const rect = this.input.getBoundingClientRect();
			const pickerRect = this.picker.getBoundingClientRect();
			const viewportWidth = window.innerWidth;
			const viewportHeight = window.innerHeight;
			const margin = 8;
			const maxWidth = Math.max(0, viewportWidth - margin * 2);
			const fallbackWidth = this.type === "datetime" ? 352 : 288;
			const width = Math.min(pickerRect.width || fallbackWidth, maxWidth);

			const spaceBottom = viewportHeight - rect.bottom;
			const spaceTop = rect.top;
			const placeAbove =
				spaceBottom < pickerRect.height && spaceTop > pickerRect.height;

			let top = placeAbove
				? rect.top - pickerRect.height - margin
				: rect.bottom + margin;
			if (top < margin) top = margin;
			if (top + pickerRect.height > viewportHeight - margin) {
				top = Math.max(margin, viewportHeight - pickerRect.height - margin);
			}

			let left = rect.left;
			if (left + width > viewportWidth - margin) {
				left = Math.max(margin, viewportWidth - width - margin);
			}
			if (left < margin) left = margin;

			this.picker.style.position = "fixed";
			this.picker.style.top = `${top}px`;
			this.picker.style.left = `${left}px`;
			this.picker.style.right = "auto";
			this.picker.style.bottom = "auto";
			this.picker.style.zIndex = "10000";
			this.picker.style.maxWidth = `${maxWidth}px`;
			if (width) {
				this.picker.style.width = `${width}px`;
			}
			this.picker.classList.toggle("origin-top", !placeAbove);
			this.picker.classList.toggle("origin-bottom", placeAbove);
		}

		renderCalendar() {
			const header = document.createElement("div");
			header.className = "flex justify-between items-center mb-3";
			const btnCls =
				"p-1 w-8 h-8 flex items-center justify-center hover:bg-slate-100 dark:hover:bg-slate-700 rounded-full text-slate-500 dark:text-slate-400";
			const prev = document.createElement("button");
			prev.type = "button";
			prev.className = btnCls;
			prev.innerHTML = "&lsaquo;";
			prev.onclick = () => {
				if (this.viewMode === "days")
					this.viewDate.setMonth(this.viewDate.getMonth() - 1);
				else if (this.viewMode === "months")
					this.viewDate.setFullYear(this.viewDate.getFullYear() - 1);
				else this.viewDate.setFullYear(this.viewDate.getFullYear() - 12);
				this.render();
			};
			const title = document.createElement("div");
			title.className = "flex items-center gap-2 text-sm";
			const yearBtn = document.createElement("button");
			yearBtn.type = "button";
			yearBtn.className =
				"font-semibold text-slate-700 dark:text-slate-200 hover:text-indigo-600";
			yearBtn.textContent = this.viewDate.getFullYear();
			yearBtn.onclick = () => {
				this.viewMode = "years";
				this.render();
			};
			if (this.type === "month" && this.viewMode === "months") {
				title.className = "text-sm font-semibold text-slate-700 dark:text-slate-200";
				title.textContent = this.viewDate.getFullYear();
			} else {
				const monthBtn = document.createElement("button");
				monthBtn.type = "button";
				monthBtn.className =
					"font-semibold text-slate-700 dark:text-slate-200 hover:text-indigo-600";
				monthBtn.textContent = this.viewDate.toLocaleString("default", {
					month: "long",
				});
				monthBtn.onclick = () => {
					this.viewMode = "months";
					this.render();
				};
				title.append(monthBtn, yearBtn);
			}
			const next = document.createElement("button");
			next.type = "button";
			next.className = btnCls;
			next.innerHTML = "&rsaquo;";
			next.onclick = () => {
				if (this.viewMode === "days")
					this.viewDate.setMonth(this.viewDate.getMonth() + 1);
				else if (this.viewMode === "months")
					this.viewDate.setFullYear(this.viewDate.getFullYear() + 1);
				else this.viewDate.setFullYear(this.viewDate.getFullYear() + 12);
				this.render();
			};
			header.append(prev, title, next);
			this.picker.appendChild(header);

			if (this.viewMode === "months") {
				this.renderMonthsGrid();
				return;
			}
			if (this.viewMode === "years") {
				this.renderYearsGrid();
				return;
			}

			const grid = document.createElement("div");
			grid.className = "grid grid-cols-7 gap-1 text-center text-sm";
			["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].forEach((d) => {
				const el = document.createElement("div");
				el.className =
					"text-[11px] text-slate-400 dark:text-slate-500 font-semibold";
				el.textContent = d;
				grid.appendChild(el);
			});

			const y = this.viewDate.getFullYear(),
				m = this.viewDate.getMonth();
			const firstDay = new Date(y, m, 1).getDay();
			const daysInMonth = new Date(y, m + 1, 0).getDate();
			const daysInPrev = new Date(y, m, 0).getDate();
			const minTime = this.minDate ? new Date(this.minDate).setHours(0, 0, 0, 0) : null;
			const maxTime = this.maxDate ? new Date(this.maxDate).setHours(0, 0, 0, 0) : null;

			for (let i = 0; i < firstDay; i++) {
				const d = document.createElement("div");
				d.className = "text-slate-300 dark:text-slate-600 text-xs";
				d.textContent = daysInPrev - firstDay + 1 + i;
				grid.appendChild(d);
			}
			for (let i = 1; i <= daysInMonth; i++) {
				const d = document.createElement("button");
				d.type = "button";
				const currentDate = new Date(y, m, i);
				const currentTime = currentDate.getTime();
				const disabled =
					(minTime && currentTime < minTime) ||
					(maxTime && currentTime > maxTime);
				const isSelected =
					this.selectedDate &&
					this.selectedDate.getDate() === i &&
					this.selectedDate.getMonth() === m &&
					this.selectedDate.getFullYear() === y;
				const now = new Date();
				const isToday =
					now.getDate() === i &&
					now.getMonth() === m &&
					now.getFullYear() === y;

				let cls =
					"w-8 h-8 rounded-full flex items-center justify-center text-sm transition ";
				if (disabled)
					cls += "text-slate-300 dark:text-slate-600 cursor-not-allowed";
				else if (isSelected) cls += "bg-indigo-600 text-white shadow";
				else if (isToday)
					cls +=
						"border border-indigo-200 dark:border-slate-600 text-indigo-600 dark:text-indigo-300";
				else
					cls +=
						"text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700";
				d.className = cls;
				d.textContent = i;
				if (!disabled) d.onclick = () => this.selectDate(currentDate);
				grid.appendChild(d);
			}
			this.picker.appendChild(grid);
		}

		renderMonthsGrid() {
			const grid = document.createElement("div");
			grid.className = "grid grid-cols-3 gap-2 text-center text-sm";
			const monthsShort = [
				"Jan",
				"Feb",
				"Mar",
				"Apr",
				"May",
				"Jun",
				"Jul",
				"Aug",
				"Sep",
				"Oct",
				"Nov",
				"Dec",
			];
			const y = this.viewDate.getFullYear();
			for (let m = 0; m < 12; m++) {
				const btn = document.createElement("button");
				btn.type = "button";
				const start = new Date(y, m, 1);
				const end = new Date(y, m + 1, 0);
				const disabled =
					(this.minDate && end < this.minDate) ||
					(this.maxDate && start > this.maxDate);
				const isSelected =
					this.selectedDate &&
					this.selectedDate.getFullYear() === y &&
					this.selectedDate.getMonth() === m;
				let cls = "py-2 rounded-lg border text-sm transition ";
				if (disabled)
					cls +=
						"text-slate-300 dark:text-slate-600 cursor-not-allowed border-slate-200 dark:border-slate-700";
				else if (isSelected)
					cls += "bg-indigo-600 text-white border-indigo-600";
				else
					cls +=
						"border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-700";
				btn.className = cls;
				btn.textContent = monthsShort[m];
				if (!disabled) {
					btn.onclick = () => {
						if (this.type === "month") {
							this.selectDate(new Date(y, m, 1));
						} else {
							this.viewDate.setMonth(m);
							this.viewMode = "days";
							this.render();
						}
					};
				}
				grid.appendChild(btn);
			}
			this.picker.appendChild(grid);
		}

		renderYearsGrid() {
			const grid = document.createElement("div");
			grid.className = "grid grid-cols-3 gap-2 text-center text-sm";
			const y = this.viewDate.getFullYear();
			const start = y - 6;
			for (let i = 0; i < 12; i++) {
				const year = start + i;
				const btn = document.createElement("button");
				btn.type = "button";
				const startDate = new Date(year, 0, 1);
				const endDate = new Date(year, 11, 31);
				const disabled =
					(this.minDate && endDate < this.minDate) ||
					(this.maxDate && startDate > this.maxDate);
				const isSelected =
					this.selectedDate && this.selectedDate.getFullYear() === year;
				let cls = "py-2 rounded-lg border text-sm transition ";
				if (disabled)
					cls +=
						"text-slate-300 dark:text-slate-600 cursor-not-allowed border-slate-200 dark:border-slate-700";
				else if (isSelected)
					cls += "bg-indigo-600 text-white border-indigo-600";
				else
					cls +=
						"border-slate-200 dark:border-slate-700 text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-700";
				btn.className = cls;
				btn.textContent = year;
				if (!disabled) {
					btn.onclick = () => {
						this.viewDate.setFullYear(year);
						this.viewMode = "months";
						this.render();
					};
				}
				grid.appendChild(btn);
			}
			this.picker.appendChild(grid);
		}

		renderTime() {
			const wrap = document.createElement("div");
			wrap.className = "flex flex-col gap-3";
			const label = document.createElement("div");
			label.className =
				"flex items-center justify-between text-xs font-semibold uppercase tracking-[0.16em] text-slate-500 dark:text-slate-400";
			label.innerHTML =
				'<span>Time</span><span class="normal-case tracking-normal text-slate-400 dark:text-slate-500">' +
				(this.is24h ? "24-hour" : "12-hour") +
				"</span>";
			wrap.appendChild(label);

			if (!this.selectedDate) this.selectedDate = new Date();
			let hours = this.selectedDate.getHours();
			const minutes = this.selectedDate.getMinutes();

			const createNumber = (val, min, max, onChange, labelText, closeOnChange = false) => {
				const field = document.createElement("label");
				field.className = "min-w-0 flex-1";
				const caption = document.createElement("span");
				caption.className = "mb-1 block text-[11px] font-semibold text-slate-500 dark:text-slate-400";
				caption.textContent = labelText;
				const el = document.createElement("input");
				el.type = "number";
				el.className =
					"h-11 w-full rounded-xl border border-slate-200 bg-slate-50 px-3 text-center font-mono text-lg font-semibold text-slate-900 outline-none transition focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white";
				el.value = val.toString().padStart(2, "0");
				el.addEventListener("click", () => el.select());
				el.addEventListener("change", (e) => {
					let v = parseInt(e.target.value, 10);
					if (isNaN(v)) v = min;
					v = Math.max(min, Math.min(max, v));
					e.target.value = v.toString().padStart(2, "0");
					onChange(v);
					if (closeOnChange && this.type === "datetime" && this.autoClose) {
						window.setTimeout(() => this.close(), 80);
					}
				});
				field.append(caption, el);
				return field;
			};

			const maxHour = this.is24h ? 23 : 12;
			const minHour = this.is24h ? 0 : 1;
			const displayHour = this.is24h ? hours : hours % 12 || 12;
			const hourInput = createNumber(displayHour, minHour, maxHour, (v) => {
				if (this.is24h) this.selectedDate.setHours(v);
				else {
					const isPM = hours >= 12;
					if (v === 12) v = 0;
					this.selectedDate.setHours(isPM ? v + 12 : v);
					hours = this.selectedDate.getHours();
				}
				this.updateInput();
			}, "Hour");
			const minInput = createNumber(minutes, 0, 59, (v) => {
				this.selectedDate.setMinutes(v);
				this.updateInput();
			}, "Minute", true);

			const row = document.createElement("div");
			row.className = "flex items-end justify-center gap-2";
			row.append(hourInput, minInput);

			if (!this.is24h) {
				const ampm = document.createElement("button");
				ampm.type = "button";
				const isPM = hours >= 12;
				ampm.textContent = isPM ? "PM" : "AM";
				ampm.className = isPM
					? "h-11 rounded-xl bg-slate-800 px-4 text-sm font-semibold text-white"
					: "h-11 rounded-xl border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-700 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200";
				ampm.onclick = () => {
					const h = this.selectedDate.getHours();
					this.selectedDate.setHours(isPM ? h - 12 : h + 12);
					this.render();
					this.updateInput();
				};
				row.appendChild(ampm);
			}

			wrap.appendChild(row);
			if (this.type === "datetime") {
				const footer = document.createElement("div");
				footer.className = "mt-1 flex items-center justify-between gap-3";
				const preview = document.createElement("div");
				preview.className = "min-w-0 truncate text-xs font-medium text-slate-500 dark:text-slate-400";
				preview.textContent = this.selectedDate ? this.formatDate(this.selectedDate) : "";
				const done = document.createElement("button");
				done.type = "button";
				done.className =
					"inline-flex h-9 items-center justify-center rounded-xl bg-indigo-600 px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-500";
				done.textContent = "Done";
				done.onclick = (e) => {
					e.preventDefault();
					e.stopPropagation();
					this.close(true);
				};
				footer.append(preview, done);
				wrap.appendChild(footer);
			}
			this.picker.appendChild(wrap);
		}

		renderQuickActions() {
			const box = document.createElement("div");
			box.className =
				"mt-3 flex flex-wrap items-center justify-end gap-2 border-t border-slate-100 pt-3 dark:border-slate-700";

			const actions = [
				{
					label: "Today",
					shiftDays: 0,
					icon:
						'<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10m-11 8h12a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>',
				},
				{
					label: "+7d",
					shiftDays: 7,
					icon:
						'<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8m4-4H8m12 0a8 8 0 11-16 0 8 8 0 0116 0z"/></svg>',
				},
				{
					label: "+30d",
					shiftDays: 30,
					icon:
						'<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12h10M12 7v10m8-5a8 8 0 11-16 0 8 8 0 0116 0z"/></svg>',
				},
			];

			actions.forEach((action) => {
				const btn = document.createElement("button");
				btn.type = "button";
				btn.className =
					"inline-flex h-8 items-center gap-1.5 rounded-lg border border-slate-200 bg-slate-50 px-2.5 text-xs font-semibold text-slate-700 transition hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-200 dark:hover:bg-slate-800";
				btn.innerHTML = action.icon + "<span>" + action.label + "</span>";
				btn.onclick = () => {
					const now = new Date();
					now.setHours(0, 0, 0, 0);
					now.setDate(now.getDate() + action.shiftDays);
					const minTime = this.minDate ? new Date(this.minDate).setHours(0, 0, 0, 0) : null;
					const maxTime = this.maxDate ? new Date(this.maxDate).setHours(23, 59, 59, 999) : null;
					if (minTime && now < minTime) return;
					if (maxTime && now > maxTime) return;
					this.viewDate = new Date(now);
					this.selectDate(now, true);
				};
				box.appendChild(btn);
			});

			this.picker.appendChild(box);
		}
	}

	function initDatePickers() {
		document.querySelectorAll("input.ts-datepicker").forEach((el) => {
			if (!el.dataset.enhanced && !el._twDatepicker) new TailwindDateTimePicker(el);
		});
	}

	if (
		document.readyState === "interactive" ||
		document.readyState === "complete"
	) {
		initDatePickers();
	} else {
		document.addEventListener("DOMContentLoaded", initDatePickers);
	}

	window.initDatePickers = initDatePickers;
})();
