<template>
	<Popover
		class="inline-block"
		:placement="placement"
		@open="initFromValue"
		@close="handleClose"
	>
		<template #target="{ togglePopover, isOpen }">
			<slot
				name="target"
				v-bind="{ togglePopover, isOpen, displayLabel, inputValue }"
			>
				<TextInput
					v-model="inputValue"
					type="text"
					class="cursor-text w-full caret-transparent"
					:class="props.inputClass"
					:label="props.label"
					:variant="props.variant"
					:placeholder="props.placeholder || 'Select date & time'"
					:disabled="props.disabled"
					:readonly="
						props.disableTextInput || props.readonly || !props.allowCustom
					"
					@focus="activateInput(isOpen, togglePopover)"
					@click="activateInput(isOpen, togglePopover)"
					@blur="onBlur"
					@keydown.enter.prevent="onEnter(togglePopover)"
				>
					<template v-if="$slots.prefix" #prefix>
						<slot
							name="prefix"
							v-bind="{ togglePopover, isOpen, displayLabel, inputValue }"
						/>
					</template>
					<template #suffix>
						<slot
							name="suffix"
							v-bind="{ togglePopover, isOpen, displayLabel, inputValue }"
						>
							<FeatherIcon
								name="chevron-down"
								class="h-4 w-4 cursor-pointer"
								@mousedown.prevent="togglePopover"
							/>
						</slot>
					</template>
				</TextInput>
			</slot>
		</template>

		<template #body="{ togglePopover }">
			<div
				ref="popoverContentRef"
				class="w-fit min-w-60 select-none text-base text-ink-gray-9 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 mt-2"
			>
				<!-- Header (Month/Year navigation) -->
				<div class="flex items-center justify-between p-2 pb-0 gap-1">
					<Button
						variant="ghost"
						size="sm"
						class="text-sm font-medium text-ink-gray-7"
						@click="cycleView"
					>
						<span v-if="view === 'date'"
							>{{ months[currentMonth] }} {{ currentYear }}</span
						>
						<span v-else-if="view === 'month'">{{ currentYear }}</span>
						<span v-else>{{ yearRangeStart }} - {{ yearRangeStart + 11 }}</span>
					</Button>
					<div class="flex items-center">
						<Button
							variant="ghost"
							icon="chevron-left"
							class="size-7"
							@click="prev"
						/>
						<Button
							v-if="!clearable"
							variant="ghost"
							class="text-xs"
							:label="'Now'"
							@click="() => handleNowClick(togglePopover)"
						/>
						<Button
							variant="ghost"
							icon="chevron-right"
							class="size-7"
							@click="next"
						/>
					</div>
				</div>

				<!-- Calendar -->
				<div class="p-2">
					<div v-if="view === 'date'" role="grid" aria-label="Calendar dates">
						<div
							class="flex items-center text-xs font-medium uppercase text-ink-gray-4 mb-1"
						>
							<div
								v-for="d in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
								:key="d"
								class="flex h-6 w-8 items-center justify-center"
							>
								{{ d }}
							</div>
						</div>
						<div v-for="(week, wi) in weeks" :key="wi" class="flex" role="row">
							<button
								v-for="dateObj in week"
								type="button"
								:key="dateObj.key"
								class="flex h-8 w-8 items-center justify-center rounded cursor-pointer text-sm focus:outline-none focus:ring-2 focus:ring-outline-gray-2"
								:class="[
									dateObj.inMonth ? 'text-ink-gray-8' : 'text-ink-gray-3',
									dateObj.isToday ? 'font-extrabold text-ink-gray-9' : '',
									dateObj.disabled
										? 'opacity-30 cursor-not-allowed hover:bg-transparent'
										: dateObj.isSelected
											? 'bg-surface-gray-6 text-ink-white hover:bg-surface-gray-6'
											: 'hover:bg-surface-gray-2',
								]"
								role="gridcell"
								:aria-selected="dateObj.isSelected ? 'true' : 'false'"
								:aria-label="
									dateObj.date.format('YYYY-MM-DD') +
									(dateObj.isToday ? ' (Today)' : '')
								"
								:disabled="dateObj.disabled"
								@click="
									!dateObj.disabled &&
									handleDateCellClick(dateObj.date, togglePopover)
								"
							>
								{{ dateObj.date.date() }}
							</button>
						</div>
					</div>
					<div
						v-else-if="view === 'month'"
						class="grid grid-cols-3 gap-1"
						role="grid"
						aria-label="Select month"
					>
						<button
							v-for="(m, i) in months"
							type="button"
							:key="m"
							class="py-2 text-sm rounded cursor-pointer text-center hover:bg-surface-gray-2 focus:outline-none focus:ring-2 focus:ring-brand-6"
							:class="{
								'bg-surface-gray-6 text-ink-white hover:bg-surface-gray-6':
									i === currentMonth,
							}"
							:aria-selected="i === currentMonth ? 'true' : 'false'"
							@click="selectMonth(i)"
						>
							{{ m.slice(0, 3) }}
						</button>
					</div>
					<div
						v-else
						class="grid grid-cols-3 gap-1"
						role="grid"
						aria-label="Select year"
					>
						<button
							v-for="y in yearRange"
							type="button"
							:key="y"
							class="py-2 text-sm rounded cursor-pointer text-center hover:bg-surface-gray-2 focus:outline-none focus:ring-2 focus:ring-brand-6"
							:class="{
								'bg-surface-gray-6 text-ink-white hover:bg-surface-gray-6':
									y === currentYear,
							}"
							:aria-selected="y === currentYear ? 'true' : 'false'"
							@click="selectYear(y)"
						>
							{{ y }}
						</button>
					</div>
				</div>

				<!-- Time Picker Section -->
				<div class="flex flex-col gap-2 p-2 pt-0">
					<TimePicker
						:value="timeValue"
						:allowCustom="props.allowCustomTime"
						:placement="'bottom-start'"
						placeholder="Select time"
						:minTime="computedMinTime"
						:maxTime="computedMaxTime"
						@change="(v) => onTimeChange(v, togglePopover)"
					/>
				</div>

				<!-- Footer Actions (clearable variant) -->
				<div
					v-if="props.clearable"
					class="flex items-center justify-between gap-1 p-2 border-t"
				>
					<div class="flex gap-1">
						<Button
							variant="outline"
							:label="'Now'"
							@click="() => handleNowClick(togglePopover)"
						/>
						<Button
							variant="outline"
							:label="'Tomorrow'"
							@click="() => handleTomorrowClick()"
						/>
					</div>
					<Button
						v-if="selectedDate"
						size="sm"
						variant="outline"
						:label="'Clear'"
						@click="() => handleClearClick(togglePopover)"
					/>
				</div>
			</div>
		</template>
	</Popover>
</template>

<script setup>
import { ref, computed, watch, toRefs } from 'vue';
import {
	Popover,
	Button,
	TextInput,
	FeatherIcon,
	dayjs,
	dayjsLocal,
} from 'frappe-ui';
import { dayjsSystem } from 'frappe-ui/src/utils/dayjs';

import TimePicker from './TimePicker.vue';
import { months, monthStart, generateWeeks, getDateValue } from './utils.js';

/*
Added a props disableTextInput
When true, the text input becomes readonly, preventing manual typing.
*/

const props = defineProps({
	value: {
		type: String,
		default: '',
	},
	modelValue: {
		type: String,
		default: '',
	},
	placement: {
		type: String,
		default: 'bottom-start',
	},
	variant: {
		type: String,
		default: 'subtle',
	},
	placeholder: {
		type: String,
		default: 'Select date & time',
	},
	disableTextInput: {
		type: Boolean,
		default: false,
	},
	readonly: {
		type: Boolean,
		default: false,
	},
	allowCustom: {
		type: Boolean,
		default: true,
	},
	autoClose: {
		type: Boolean,
		default: true,
	},
	disabled: {
		type: Boolean,
		default: false,
	},
	clearable: {
		type: Boolean,
		default: true,
	},
	allowCustomTime: {
		type: Boolean,
		default: true,
	},
	format: {
		type: String,
		default: '',
	},
	minDateTime: {
		type: String,
		default: '',
	},
	maxDateTime: {
		type: String,
		default: '',
	},
	label: {
		type: String,
		default: '',
	},
	inputClass: {
		type: String,
		default: '',
	},
});
const emit = defineEmits(['update:modelValue', 'change', 'open', 'close']);

const { autoClose } = toRefs(props);

const view = ref('date');
const currentYear = ref(dayjs().year());
const currentMonth = ref(dayjs().month());
const DATE_FORMAT = 'YYYY-MM-DD';
const DATE_TIME_FORMAT = 'YYYY-MM-DD h:mm a';

const selectedDate = ref(''); // YYYY-MM-DD
const timeValue = ref(''); // HH:mm:ss

const initialValue = ref(props.modelValue || props.value || '');

function coerceDateTime(val) {
	if (!val) return null;
	const raw = String(val).trim();
	if (!raw) return null;

	if (props.format) {
		const dStrict = dayjs(raw, props.format, true);
		if (dStrict.isValid()) return dStrict;
	}

	const dLoose = dayjsLocal(raw);
	if (dLoose.isValid()) return dLoose;

	const normalized = getDateValue(raw);
	if (normalized) {
		const dNorm = dayjsLocal(normalized);
		if (dNorm.isValid()) return dNorm;
	}
	return null;
}

function syncFromValue(val) {
	if (!val) {
		if (!props.clearable) {
			const now = dayjsLocal();
			currentYear.value = now.year();
			currentMonth.value = now.month();
			selectedDate.value = now.format(DATE_FORMAT);
			timeValue.value = now.format('HH:mm:ss');
		} else {
			selectedDate.value = '';
			timeValue.value = '';
		}
		return;
	}
	const d = coerceDateTime(val);
	if (!d) {
		selectedDate.value = '';
		timeValue.value = '';
		return;
	}
	currentYear.value = d.year();
	currentMonth.value = d.month();
	selectedDate.value = d.format(DATE_FORMAT);
	timeValue.value = d.format('HH:mm:ss');
}

syncFromValue(initialValue.value);

function initFromValue() {
	syncFromValue(props.modelValue || props.value);
}

watch(
	() => [props.modelValue, props.value],
	([m, v]) => {
		const val = m || v;
		syncFromValue(val);
	},
);

const combinedValue = computed(() => {
	if (!selectedDate.value) return '';
	const base = `${selectedDate.value} ${timeValue.value || '00:00:00'}`;
	const local = dayjs(base);
	if (!local.isValid()) return '';
	return local.format(DATE_TIME_FORMAT);
});

const displayLabel = computed(() => {
	if (!combinedValue.value) return '';
	if (props.format) return dayjs(combinedValue.value).format(props.format);
	return combinedValue.value;
});

const inputValue = ref(displayLabel.value);
const isTyping = ref(false);
watch(displayLabel, (val) => {
	if (!isTyping.value) inputValue.value = val;
});

function maybeClose(togglePopover, condition = true) {
	if (condition && autoClose.value && togglePopover) togglePopover();
}

function clearSelection() {
	if (!selectedDate.value && !timeValue.value) return;
	selectedDate.value = '';
	timeValue.value = '';
	emit('update:modelValue', '');
	emit('change', '');
	initialValue.value = '';
	inputValue.value = '';
}

function commitInput(close = false, togglePopover) {
	const raw = inputValue.value.trim();
	if (!raw) {
		if (!props.clearable) {
			const now = dayjsLocal();
			selectDate(now);
			timeValue.value = now.format('HH:mm:ss');
			emitChange();
			maybeClose(togglePopover, close);
		} else {
			clearSelection();
			maybeClose(togglePopover, close);
		}
		return;
	}
	const parsed = coerceDateTime(raw);
	if (parsed) {
		selectDate(parsed);
		timeValue.value = parsed.format('HH:mm:ss');
		emitChange();
		maybeClose(togglePopover, close);
	}
}

const popoverContentRef = ref(null);
function onBlur(e) {
	const next = e.relatedTarget;
	if (next && popoverContentRef.value?.contains(next)) return;
	commitInput();
	isTyping.value = false;
}
function onEnter(togglePopover) {
	commitInput(true, togglePopover);
	isTyping.value = false;
}
function activateInput(isOpen, togglePopover) {
	if (!props.disableTextInput) {
		isTyping.value = true;
	}

	if (!isOpen) togglePopover();
}

const minDT = computed(() =>
	props.minDateTime ? coerceDateTime(props.minDateTime) : null,
);
const maxDT = computed(() =>
	props.maxDateTime ? coerceDateTime(props.maxDateTime) : null,
);

function dateDisabled(d) {
	if (minDT.value && d.endOf('day').isBefore(minDT.value)) return true;
	if (maxDT.value && d.startOf('day').isAfter(maxDT.value)) return true;
	return false;
}

const weeks = computed(() => {
	const base = generateWeeks(
		currentYear.value,
		currentMonth.value,
		selectedDate.value,
	);
	return base.map((week) =>
		week.map((obj) => ({ ...obj, disabled: dateDisabled(obj.date) })),
	);
});

const computedMinTime = computed(() => {
	if (!minDT.value || !selectedDate.value) return '';
	if (dayjs(selectedDate.value).isSame(minDT.value, 'day'))
		return minDT.value.format('HH:mm:ss');
	return '';
});
const computedMaxTime = computed(() => {
	if (!maxDT.value || !selectedDate.value) return '';
	if (dayjs(selectedDate.value).isSame(maxDT.value, 'day'))
		return maxDT.value.format('HH:mm:ss');
	return '';
});

watch([computedMinTime, computedMaxTime, timeValue, selectedDate], () => {
	if (!selectedDate.value || !timeValue.value) return;
	const cur = dayjs(`${selectedDate.value} ${timeValue.value}`);
	if (minDT.value && cur.isBefore(minDT.value))
		timeValue.value = computedMinTime.value || timeValue.value;
	if (maxDT.value && cur.isAfter(maxDT.value))
		timeValue.value = computedMaxTime.value || timeValue.value;
});

function selectDate(date) {
	const d = dayjs(date);
	if (!d.isValid()) return;
	if (dateDisabled(d)) return;
	selectedDate.value = d.format(DATE_FORMAT);
	currentYear.value = d.year();
	currentMonth.value = d.month();
}
function selectMonth(i) {
	currentMonth.value = i;
	view.value = 'date';
}
function selectYear(y) {
	currentYear.value = y;
	view.value = 'month';
}
function prev() {
	if (view.value === 'date') {
		const m = monthStart(currentYear.value, currentMonth.value).subtract(
			1,
			'month',
		);
		currentYear.value = m.year();
		currentMonth.value = m.month();
	} else if (view.value === 'month') currentYear.value -= 1;
	else currentYear.value -= 12;
}
function next() {
	if (view.value === 'date') {
		const m = monthStart(currentYear.value, currentMonth.value).add(1, 'month');
		currentYear.value = m.year();
		currentMonth.value = m.month();
	} else if (view.value === 'month') currentYear.value += 1;
	else currentYear.value += 12;
}
function handleDateCellClick(date, togglePopover) {
	selectDate(date);
	emitChange(true, togglePopover);
	isTyping.value = false;
	view.value = 'date';
}

function onTimeChange(val, togglePopover) {
	timeValue.value = val;
	isTyping.value = false;
	if (selectedDate.value) emitChange(true, togglePopover);
}

function emitChange(close = false, togglePopover) {
	if (!selectedDate.value) {
		clearSelection();
		return;
	}

	const localDateTime = combinedValue.value;
	const systemDateTime = dayjsSystem(localDateTime).format(DATE_TIME_FORMAT);

	if (systemDateTime !== initialValue.value) {
		emit('update:modelValue', systemDateTime);
		emit('change', systemDateTime);
		initialValue.value = systemDateTime;
	}

	if (!isTyping.value) inputValue.value = displayLabel.value;
	maybeClose(togglePopover, close);
}

function handleNowClick(togglePopover) {
	const now = dayjsLocal();
	selectDate(now);
	timeValue.value = now.format('HH:mm:ss');
	emitChange(true, togglePopover);
	isTyping.value = false;
}
function handleTomorrowClick() {
	const tomorrow = dayjsLocal().add(1, 'day');
	selectDate(tomorrow);
	// keep current time value if any, else set to now's time
	if (!timeValue.value) timeValue.value = dayjsLocal().format('HH:mm:ss');
	emitChange();
	isTyping.value = false;
}
function handleClearClick(togglePopover) {
	clearSelection();
	maybeClose(togglePopover);
	isTyping.value = false;
	view.value = 'date';
}

function cycleView() {
	if (view.value === 'date') view.value = 'month';
	else if (view.value === 'month') view.value = 'year';
	else view.value = 'date';
}
function handleClose() {
	view.value = 'date';
	if (isTyping.value) {
		commitInput();
		isTyping.value = false;
	}
}

const yearRangeStart = computed(
	() => currentYear.value - (currentYear.value % 12),
);
const yearRange = computed(() =>
	Array.from({ length: 12 }, (_, i) => yearRangeStart.value + i),
);
</script>
