<!--
  - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
  - SPDX-License-Identifier: AGPL-3.0-or-later
-->

<docs>
In general it is recommended to use the native date picker (see `NcDateTimePickerNative` which is based on `<input type="date">`).
But some use cases are not covered by the native date selector, like selecting ranges or selecting a timezone.
For those cases this component can be used.

### General examples
```vue
<template>
	<div class="wrapper">
		<fieldset class="type-select">
			<legend>Picker mode</legend>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="date">Date</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="datetime">Date and time</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="week">Week</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="month">Month</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="year">Year</NcCheckboxRadioSwitch>
		</fieldset>
		<NcDateTimePicker
			v-model="time"
			:type />
		<span>{{ time }}</span>
	</div>
</template>
<script>
export default {
	data() {
		return {
			type: 'date',
			time: new Date('2022-10-10 10:10:10'),
		}
	},
}
</script>
<style scoped>
.wrapper {
	display: flex;
	flex-direction: column;
}

.type-select {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
}
</style>
```

### Example with confirm button

By default the date is applied as soon as you select the day in the calendar.
Sometimes - especially when selecting date and time - it is required to only emit the selected date when the flow is finished.
For this the `confirm` prop can be used, this will add a confirmation button to the selector.

```vue
<template>
	<span>
		<NcDateTimePicker
			v-model="time"
			type="datetime"
			confirm />
		{{ time }}
	</span>
</template>
<script>
	export default {
		data() {
			return {
				time: null,
			}
		},
	}
</script>
```

### Range picker

The most common use case for the `NcDateTimePicker` is picking a range, as this is not supported by the native date picker.

When selecting the range picker type, the model value type will change from `Date` to `[Date, Date]`.
Meaning an array with two dates is used, the first date is the range start and the second date is the range end.

```vue
<template>
	<div>
		<fieldset class="type-select">
			<legend>Picker mode</legend>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="date-range">Date</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="time-range">Time</NcCheckboxRadioSwitch>
			<NcCheckboxRadioSwitch v-model="type" type="radio" value="datetime-range">Date and time</NcCheckboxRadioSwitch>
		</fieldset>

		<NcDateTimePicker
			:key="type"
			v-model="time"
			:type />
		<div>
			<div>Start: {{ formatDate(time[0]) }}</div>
			<div>End: {{ formatDate(time[1]) }}</div>
		</div>
	</div>
</template>
<script>
export default {
	data() {
		return {
			time: [new Date(2025, 3, 18, 12, 30), new Date(2025, 3, 21, 13, 30)],
			type: 'date-range',
		}
	},
	methods: {
		formatDate(date) {
			const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
			const timeString = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
			if (this.type === 'date-range') {
				return dateString
			} else if (this.type === 'time-range') {
				return timeString
			}
			return `${dateString} ${timeString}`
		},
	},
}
</script>

<style scoped>
.type-select {
	display: flex;
	flex-direction: row;
	flex-wrap: wrap;
}
</style>
```

### Time zone aware date picker

The datepicker can optionally include a picker for the preferred time zone. The selected time does not factor in the
picked time zone, but you will have to convert it yourself when necessary.

```vue
<template>
	<span>
		<NcDateTimePicker
			v-model="time"
			type="datetime"
			show-timezone-select
			v-model:timezone-id="tz" /><br>
		{{ time }} | {{ tz }}
	</span>
</template>
<script>
export default {
	data() {
		return {
			time: undefined,
			tz: 'Europe/Berlin',
		}
	},
}
</script>
```

### Restricting to a specific time range

You can optionally restrict the selectable time range, for example from 08:00 on Monday to 20:00 on Sunday of the current week.
This example shows how the `min` and `max` properties are applied to different picker types.

```vue
<template>
	<div>
		<h4>Date and time</h4>
		<NcDateTimePicker
			v-model="selectedDatetime"
			type="datetime"
			:min="min"
			:max="max" />
		<p>Selected: {{ selectedDatetime }}</p>

		<h4>Date range</h4>
		<NcDateTimePicker
			v-model="selectedDateRange"
			type="date-range"
			:min="min"
			:max="max" />
		<p>Selected: {{ selectedDateRange }}</p>

		<h4>Date and time range</h4>
		<NcDateTimePicker
			v-model="selectedDatetimeRange"
			type="datetime-range"
			:min="min"
			:max="max" />
		<p>Selected: {{ selectedDatetimeRange }}</p>

		<h4>Time range</h4>
		<NcDateTimePicker
			v-model="selectedTimeRange"
			type="time-range"
			:min="min"
			:max="max" />
		<p>Selected: {{ selectedTimeRange }}</p>

		<h4>Time</h4>
		<NcDateTimePicker
			v-model="selectedTime"
			type="time"
			:min="min"
			:max="max" />
		<p>Selected: {{ selectedTime }}</p>
	</div>
</template>
<script>
export default {
	data() {
		const now = new Date()
		const day = now.getDay()
		const daysSinceMonday = (day + 6) % 7

		const startOfWeek = new Date(now)
		startOfWeek.setDate(now.getDate() - daysSinceMonday)
		startOfWeek.setHours(8, 0, 0, 0)

		const endOfWeek = new Date(startOfWeek)
		endOfWeek.setDate(startOfWeek.getDate() + 6)
		endOfWeek.setHours(20, 0, 0, 0)

		return {
			selectedDatetime: null,
			selectedDateRange: null,
			selectedDatetimeRange: null,
			selectedTimeRange: null,
			selectedTime: null,
			min: startOfWeek,
			max: endOfWeek,
		}
	},
}
</script>
```

### Inline calendar

Renders the calendar inline (without the text input) inside your parent component.

```vue
<template>
	<div>
		<NcDateTimePicker v-model="date" inline />
		<p>Selected: {{ date }}</p>
	</div>
</template>
<script>
export default {
	data() {
		return { date: new Date() }
	},
}
</script>
```
</docs>

<script setup lang="ts">
import type {
	// The accepted model value
	ModelValue as LibraryModelValue,
	// The emitted object for time picker
	TimeObj as LibraryTimeObject,
	VueDatePickerProps,
} from '@vuepic/vue-datepicker'

import {
	mdiCalendarBlank,
	mdiChevronDown,
	mdiChevronLeft,
	mdiChevronRight,
	mdiChevronUp,
	mdiClock,
	mdiClose,
} from '@mdi/js'
import {
	getCanonicalLocale,
	getDayNames,
	getDayNamesMin,
	getFirstDay,
} from '@nextcloud/l10n'
import VueDatePicker from '@vuepic/vue-datepicker'
import { parse } from 'date-fns/parse'
import { computed, useTemplateRef, warn, watchEffect } from 'vue'
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
import NcTimezonePicker from '../NcTimezonePicker/NcTimezonePicker.vue'
import { t } from '../../l10n.ts'
import NcButton from '../NcButton/index.ts'
import { getDateFormat, getDateTimeFormat, getMonthFormat, getTimeFormat, getWeekFormat, getYearFormat } from './format.ts'
import useDateFnsLocale from './useDateFnsLocale.ts'

type LibraryFormatOptions = VueDatePickerProps['format']
type LibraryTextInputOptions = VueDatePickerProps['textInput']

/**
 * The preselected IANA time zone ID for the time zone picker,
 * only relevant in combination with `show-timezone-select`.
 * The prop supports two-way binding through v-model directive.
 *
 * @example `Europe/Berlin`
 * @default 'UTC'
 */
const timezoneId = defineModel<string>('timezoneId', { default: 'UTC' })

const props = withDefaults(defineProps<{
	/**
	 * If set to true the menu will be placed on the `<body>`
	 * instead of default placement on the picker.
	 */
	appendToBody?: boolean

	/**
	 * Aria label for the input box.
	 *
	 * @default 'Datepicker input'
	 */
	ariaLabel?: string

	/**
	 * Aria label for the date picker menu.
	 *
	 * @default 'Datepicker menu'
	 */
	ariaLabelMenu?: string

	/**
	 * Allow to clear the input.
	 *
	 * @default false
	 */
	clearable?: boolean

	/**
	 * Do not auto-apply the date but require clicking the confirmation button.
	 *
	 * @default false
	 */
	confirm?: boolean

	/**
	 * Format for the picker input field.
	 * Can either be a string of Unicode tokens or a function that takes a Date object
	 * or for range picker an array of two dates, and returns the formatted date as string.
	 *
	 * If no format is provided, localized date and time formats are used.
	 * If a function is provided users cannot use text input.
	 *
	 * @see https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
	 */
	format?: string | ((date: Date) => string) | ((dates: [Date, Date]) => string)

	/**
	 * The locale to use for formatting the shown dates.
	 *
	 * @deprecated This property is no longer used and will be ignored because this component will always use the current Nextcloud locale.
	 * @see https://github.com/nextcloud-libraries/nextcloud-vue/issues/8639
	 */
	locale?: string

	/**
	 * The maximum date that can be selected.
	 */
	max?: Date

	/**
	 * The minimum date that can be selected.
	 */
	min?: Date

	/**
	 * Default increment step for minutes in the time picker.
	 *
	 * @default 10
	 */
	minuteStep?: number

	/**
	 * The value to initialize, but also two-way bind the selected date. The date is – like the `Date` object in
	 * JavaScript – tied to UTC. The selected time zone does not have an influence of the selected time and date
	 * value. You have to translate the time yourself when you want to factor in time zones.
	 *
	 * When using the range picker then an array containing the start and end date needs to be passed.
	 */
	modelValue?: Date | [Date, Date] | null

	/**
	 * Optional custom placeholder for the input box.
	 */
	placeholder?: string

	/**
	 * Include a timezone picker within the menu.
	 * Please note that the dates are still bound to the locale timezone
	 * and any conversion needs to be done by the app itself.
	 *
	 * @default false
	 */
	showTimezoneSelect?: boolean

	/**
	 * Show the ISO week numbers within the calendar.
	 *
	 * @default false
	 */
	showWeekNumber?: boolean

	/**
	 * Type of the picker.
	 * There is some special handling for ranges as those types require a `[Date, Date]` model value.
	 * - The 'date-range' type will enable a range picker for dates
	 * - The 'time-range' allows picking a time range.
	 * - The 'datetime-range' allows picking dates with times assigned.
	 *
	 * @default 'date'
	 */
	type?: 'date' | 'datetime' | 'time' | 'week' | 'month' | 'year' | 'date-range' | 'time-range' | 'datetime-range'

	/**
	 * Render the calendar inline (no input field).
	 *
	 * @default false
	 */
	inline?: boolean
}>(), {
	ariaLabel: t('Datepicker input'),
	ariaLabelMenu: t('Datepicker menu'),
	format: undefined,
	locale: undefined,
	max: undefined,
	min: undefined,
	minuteStep: 10,
	timezoneId: 'UTC',
	modelValue: null,
	// set by fallbackPlaceholder
	placeholder: undefined,
	type: 'date',
	inline: false,
})

const emit = defineEmits<{
	/**
	 * If range picker is enabled then an array containing start and end date are emitted.
	 * Otherwise the selected date is emitted.
	 * `null` is emitted if `clearable` is set to `true` and the value was cleared.
	 */
	'update:modelValue': [Date | [Date, Date] | null]
	'update:timezoneId': [string]
	/**
	 * Input blur
	 */
	blur: []
}>()

const realLocale = getCanonicalLocale()
watchEffect(() => {
	if (props.locale !== undefined) {
		warn('[NcDateTimePicker] The `locale` property is no longer used and will be ignored.')
	}
})

const targetElement = useTemplateRef('target')
const pickerInstance = useTemplateRef('picker')

/**
 * Mapping of the model-value prop to the format expected by the library.
 * We do not directly pass the prop and adjust the interface to not transparently wrap the library.
 * This has show as beeing a pain in the past when we need to switch underlying libraries.
 */
const value = computed<LibraryModelValue>(() => {
	if (props.modelValue === null && props.clearable) {
		return null
	}

	if (props.type === 'week') {
		const date = props.modelValue instanceof Date ? props.modelValue : new Date()
		const end = new Date(date)
		end.setUTCDate(date.getUTCDate() + 6)
		return [date, end]
	} else if (props.type === 'year') {
		const date = props.modelValue instanceof Date ? props.modelValue : new Date()
		return date.getUTCFullYear()
	} else if (props.type === 'month') {
		const date = props.modelValue instanceof Date ? props.modelValue : new Date()
		return { year: date.getUTCFullYear(), month: date.getUTCMonth() }
	} else if (props.type === 'time') {
		const time = props.modelValue instanceof Date ? props.modelValue : new Date()
		return {
			hours: time.getHours(),
			minutes: time.getMinutes(),
			seconds: time.getSeconds(),
		} satisfies LibraryTimeObject
	} else if (props.type === 'time-range') {
		const time = [props.modelValue].flat()
		if (time.length !== 2) {
			const start = new Date()
			const end = new Date(start)
			end.setHours(end.getHours() + 1)
			time.splice(0, 2, start, end)
		}

		return (time as [Date, Date]).map((date) => ({
			hours: date.getHours(),
			minutes: date.getMinutes(),
			seconds: date.getSeconds(),
		} as LibraryTimeObject))
	} else if (props.type.endsWith('-range')) {
		if (props.modelValue === undefined) {
			const start = new Date()
			const end = new Date(start)
			end.setUTCDate(start.getUTCDate() + 7)
			return [start, end]
		}
		return props.modelValue
	}

	// no special handling for other types needed
	return props.modelValue ?? new Date()
})

const placeholderFallback = computed(() => {
	if (props.type === 'date') {
		return t('Select date')
	} else if (props.type === 'time') {
		return t('Select time')
	} else if (props.type === 'datetime') {
		return t('Select date and time')
	} else if (props.type === 'week') {
		return t('Select week')
	} else if (props.type === 'month') {
		return t('Select month')
	} else if (props.type === 'year') {
		return t('Select year')
	} else if (props.type.endsWith('-range')) {
		return t('Select time range')
	}
	// should not be reached
	return t('Select date and time')
})

const dateFnsLocale = useDateFnsLocale(realLocale)

/**
 * The date (time) formatting to be used by the library.
 * We use the provided format if possible, otherwise we provide a localized formatting
 * using `date-fns/locale' which is mostly similiar to Intl.DateTime formating.
 */
const realFormat = computed<LibraryFormatOptions>(() => {
	if (props.format) {
		// we can cast the type here as in this case its either string
		// function `(Date) => string` or `([Date, Date]) => string` where we cast to `(Date[]) => string` here.
		return props.format as LibraryFormatOptions
	}

	switch (props.type) {
		case 'date':
		case 'date-range':
			return getDateFormat(dateFnsLocale.value)
		case 'time':
		case 'time-range':
			return getTimeFormat()
		case 'datetime':
		case 'datetime-range':
			return getDateTimeFormat(dateFnsLocale.value)
		case 'month':
			return getMonthFormat(dateFnsLocale.value)
		case 'year':
			return getYearFormat(dateFnsLocale.value)
		case 'week':
			return getWeekFormat()
	}

	// fallback to default formatting
	return undefined
})

const textInput = computed<LibraryTextInputOptions>(() => {
	const realFormatVal = realFormat.value
	if (typeof realFormatVal === 'function') {
		// NOTE We could provide a format string through `textInput.format`.
		// Then Vuepic uses it when the user focuses the input.
		// But this feature has a bug.
		// It does not respect the provided locale when formatting.
		// See https://github.com/Vuepic/vue-datepicker/issues/1286
		//
		// Possible workarounds that seem not to be worth implementing for now:
		// - replace input value when user focuses the input by ourselves

		// Disable text input if user provided a format function.
		// We do not know how to parse such values.
		return false
	}

	if (typeof realFormatVal === 'string') {
		return {
			// This should not be required in Vuepic because `format` is automatically used for parsing.
			// But v11 has a bug because the format string ist cut off wrongly.
			// This is fixed in v12.1.0 and can then be simplified.
			// See https://github.com/Vuepic/vue-datepicker/issues/1208
			format: (value: string) => {
				return parse(value, realFormatVal, new Date(), { locale: (dateFnsLocale.value) })
			},
		}
	}

	return true
})

const pickerType = computed(() => ({
	timePicker: props.type === 'time' || props.type === 'time-range',
	yearPicker: props.type === 'year',
	monthPicker: props.type === 'month',
	weekPicker: props.type === 'week',
	range: props.type.endsWith('-range') && {
		// do not use partial ranges (meaning after selecting the start [Date, null] will be emitted)
		// if this is needed someday we can enable it,
		// but its not covered by our component interface (props / events) documentation so just disabled for now.
		partialRange: false,
	},
	enableTimePicker: !(props.type === 'date' || props.type === 'date-range'),
	flow: props.type === 'datetime'
		? ['calendar', 'time'] as ['calendar', 'time']
		: undefined,
}))

// Convert min/max Date into VueDatePicker's time object shape
const minTime = computed(() => props.min && { hours: props.min.getHours(), minutes: props.min.getMinutes(), seconds: props.min.getSeconds() })
const maxTime = computed(() => props.max && { hours: props.max.getHours(), minutes: props.max.getMinutes(), seconds: props.max.getSeconds() })

/**
 * Called on model value update of the library.
 *
 * @param value The value emitted from the underlying library
 */
function onUpdateModelValue(value: LibraryModelValue): void {
	if (value === null) {
		return emit('update:modelValue', null)
	}

	if (props.type === 'time') {
		// time is provided as an object
		emit('update:modelValue', formatLibraryTime(value as LibraryTimeObject))
	} else if (props.type === 'time-range') {
		// same as time but as an array with two elements
		const start = formatLibraryTime(value[0])
		const end = formatLibraryTime(value[1])
		// ensure end is beyond the start
		if (end.getTime() < start.getTime()) {
			end.setDate(end.getDate() + 1)
		}
		emit('update:modelValue', [start, end])
	} else if (props.type === 'month') {
		// month is emitted as an object with month and year attribute
		const data = value as { month: number, year: number }
		emit('update:modelValue', new Date(data.year, data.month, 1))
	} else if (props.type === 'year') {
		// Years are emitted as the numeric year e.g. 2022
		emit('update:modelValue', new Date(value as number, 0))
	} else if (props.type === 'week') {
		// weeks are emitted as [Date, Date]
		emit('update:modelValue', value[0])
	} else {
		// otherwise it already emits the correct format
		emit('update:modelValue', value as Date | [Date, Date])
	}
}

/**
 * Format a vuepick time object to native JS Date object.
 *
 * @param time - The library time value object
 */
function formatLibraryTime(time: LibraryTimeObject): Date {
	const date = new Date()
	date.setHours(time.hours)
	date.setMinutes(time.minutes)
	date.setSeconds(time.seconds)
	return date
}

// Localization

const weekStart = getFirstDay()

const dayNames = [...getDayNamesMin()]
// see https://github.com/Vuepic/vue-datepicker/issues/1159
for (let i = 0; i < weekStart; i++) {
	dayNames.push(dayNames.shift() as string)
}

// TRANSLATORS: A very short abbrevation used as a heading for "week number"
const weekNumName = t('W')

const ariaLabels = computed(() => ({
	toggleOverlay: t('Toggle overlay'),
	menu: props.ariaLabelMenu,
	input: props.ariaLabel,
	openTimePicker: t('Open time picker'),
	closeTimePicker: t('Close time Picker'),
	incrementValue: (type: 'hours' | 'minutes' | 'seconds') => {
		if (type === 'hours') {
			return t('Increment hours')
		} else if (type === 'minutes') {
			return t('Increment minutes')
		}
		return t('Increment seconds')
	},
	decrementValue: (type: 'hours' | 'minutes' | 'seconds') => {
		if (type === 'hours') {
			return t('Decrement hours')
		} else if (type === 'minutes') {
			return t('Decrement minutes')
		}
		return t('Decrement seconds')
	},
	openTpOverlay: (type: 'hours' | 'minutes' | 'seconds') => {
		if (type === 'hours') {
			return t('Open hours overlay')
		} else if (type === 'minutes') {
			return t('Open minutes overlay')
		}
		return t('Open seconds overlay')
	},
	amPmButton: t('Switch AM/PM mode'),
	openYearsOverlay: t('Open years overlay'),
	openMonthsOverlay: t('Open months overlay'),
	nextMonth: t('Next month'),
	prevMonth: t('Previous month'),
	nextYear: t('Next year'),
	prevYear: t('Previous year'),
	weekDay: (day: number) => getDayNames()[day],
	clearInput: t('Clear value'),
	calendarIcon: t('Calendar icon'),
	timePicker: t('Time picker'),
	monthPicker: (overlay: boolean) => overlay ? t('Month picker overlay') : t('Month picker'),
	yearPicker: (overlay: boolean) => overlay ? t('Year picker overlay') : t('Year picker'),
}))

/**
 * Select the current value.
 * This is used by the confirmation button if `confirmation` was set.
 */
function selectDate() {
	pickerInstance.value!.selectDate()
}

/**
 * Cancel the current selection by closing the overlay.
 * This is used by the confirmation button if `confirmation` was set.
 */
function cancelSelection() {
	pickerInstance.value!.closeMenu()
}

/**
 * Calculate the min/max time restrictions based on the selected date(s) and provided min/max dates.
 */
const calcMinMaxTime = computed(() => {
	if (props.type === 'datetime') {
		return {
			minDate: props.min,
			maxDate: props.max,
			minTime: props.min && value.value && sameDay(props.min, value.value as Date) ? minTime.value : undefined,
			maxTime: props.max && value.value && sameDay(props.max, value.value as Date) ? maxTime.value : undefined,
		}
	}

	if (props.type === 'datetime-range') {
		return {
			minDate: props.min,
			maxDate: props.max,
			minTime: props.min && value.value ? (sameDay(props.min, value.value[0] as Date) ? minTime.value : undefined) : undefined,
			maxTime: props.max && value.value ? (sameDay(props.max, value.value[1] as Date) ? maxTime.value : undefined) : undefined,
		}
	}

	if (props.type === 'time' || props.type === 'time-range') {
		return {
			minTime: props.min ? minTime.value : undefined,
			maxTime: props.max ? maxTime.value : undefined,
		}
	}

	return {
		minDate: props.min,
		maxDate: props.max,
	}
})

/**
 * Check if two dates are on the same day.
 *
 * @param a - First date
 * @param b - Second date
 */
function sameDay(a: Date, b: Date): boolean {
	return (
		a.getFullYear() === b.getFullYear()
		&& a.getMonth() === b.getMonth()
		&& a.getDate() === b.getDate()
	)
}
</script>

<template>
	<div class="vue-date-time-picker__wrapper">
		<!-- Setting :key="dateFnsLocale.code" forces the component to rerender when `:formatLocale` changes.
             Without it, the formatted date only changes after the user focuses on the text input.
             This issue was only observed with :format="realFormat" being a pattern, e.g., 'dd MMM yyyy'.
             See https://github.com/Vuepic/vue-datepicker/issues/1284  -->
		<VueDatePicker
			:key="dateFnsLocale?.code"
			ref="picker"
			:aria-labels
			:autoApply="!confirm"
			class="vue-date-time-picker"
			:class="{ 'vue-date-time-picker--clearable': clearable }"
			:cancelText="t('Cancel')"
			:clearable
			:dayNames
			:placeholder="placeholder ?? placeholderFallback"
			:format="realFormat"
			:locale="realLocale"
			:formatLocale="dateFnsLocale"
			:minDate="calcMinMaxTime.minDate"
			:maxDate="calcMinMaxTime.maxDate"
			:minTime="calcMinMaxTime.minTime"
			:maxTime="calcMinMaxTime.maxTime"
			:minutesIncrement="minuteStep"
			:modelValue="value"
			:nowButtonLabel="t('Now')"
			:selectText="t('Pick')"
			sixWeeks="fair"
			:inline
			:teleport="appendToBody ? (targetElement || undefined) : false"
			:textInput
			:weekNumName
			:weekNumbers="showWeekNumber ? { type: 'iso' } : undefined"
			:weekStart
			v-bind="pickerType"
			@update:modelValue="onUpdateModelValue"
			@blur="emit('blur')">
			<template #action-buttons>
				<NcButton size="small" variant="tertiary" @click="cancelSelection">
					{{ t('Cancel') }}
				</NcButton>
				<NcButton size="small" variant="primary" @click="selectDate">
					{{ t('Pick') }}
				</NcButton>
			</template>
			<template #clear-icon="{ clear }">
				<NcButton
					:aria-label="t('Clear value')"
					variant="tertiary-no-background"
					@click="clear">
					<template #icon>
						<NcIconSvgWrapper inline :path="mdiClose" :size="20" />
					</template>
				</NcButton>
			</template>
			<template #input-icon>
				<NcIconSvgWrapper :path="mdiCalendarBlank" :size="20" />
			</template>
			<template #clock-icon>
				<NcIconSvgWrapper inline :path="mdiClock" :size="20" />
			</template>
			<template #arrow-left>
				<NcIconSvgWrapper inline :path="mdiChevronLeft" :size="20" />
			</template>
			<template #arrow-right>
				<NcIconSvgWrapper inline :path="mdiChevronRight" :size="20" />
			</template>
			<template #arrow-down>
				<NcIconSvgWrapper inline :path="mdiChevronDown" :size="20" />
			</template>
			<template #arrow-up>
				<NcIconSvgWrapper inline :path="mdiChevronUp" :size="20" />
			</template>
			<template v-if="showTimezoneSelect" #action-extra>
				<NcTimezonePicker
					v-model="timezoneId"
					class="vue-date-time-picker__timezone"
					:appendToBody="false"
					:inputLabel="t('Time zone')" />
			</template>
		</VueDatePicker>
		<Teleport to="body" :disabled="!appendToBody">
			<div ref="target" class="vue-date-time-picker__wrapper vue-date-time-picker__wrapper--teleport" />
		</Teleport>
	</div>
</template>

<style scoped lang="scss">
@use "sass:meta";

.vue-date-time-picker__wrapper {
	// This is under :root in @vuepic/vue-datepicker/dist/main.css, so importing it scoped won't work
	--dp-common-transition: all var(--animation-quick) ease-in;
	--dp-menu-padding: 6px 8px;
	--dp-animation-duration: var(--animation-quick);
	--dp-menu-appear-transition-timing: cubic-bezier(.4, 0, 1, 1);
	--dp-transition-timing: ease-out;
	--dp-action-row-transtion: all 0.2s ease-in;
	--dp-font-family: var(--font-face);
	--dp-border-radius: var(--border-radius-element);
	--dp-cell-border-radius: var(--border-radius-small);
	--dp-transition-length: 22px;
	--dp-transition-timing-general: var(--animation-quick);
	--dp-button-height: var(--default-clickable-area);
	--dp-month-year-row-height: var(--default-clickable-area);
	--dp-month-year-row-button-size: var(--clickable-area-small);
	--dp-button-icon-height: 20px;
	--dp-calendar-wrap-padding: 0 5px;
	--dp-cell-size: var(--default-clickable-area);
	--dp-cell-padding: 5px;
	--dp-common-padding: 10px;
	--dp-input-icon-padding: var(--default-clickable-area);
	--dp-input-padding: 6px 12px;
	--dp-menu-min-width: 260px;
	--dp-action-buttons-padding: 1px 6px;
	--dp-row-margin: 5px 0;
	--dp-calendar-header-cell-padding: 0.5rem;
	--dp-multi-calendars-spacing: 10px;
	--dp-overlay-col-padding: 3px;
	--dp-time-inc-dec-button-size: var(--default-clickable-area);
	--dp-font-size: 1rem;
	--dp-preview-font-size: var(--font-size-small);
	--dp-time-font-size: 2rem;
	--dp-action-button-height: var(--clickable-area-small);
	--dp-action-row-padding: 8px;
	--dp-direction: ltr;

	// We need to import the vuepic styles, but at least scoped to our class and scope
	// plain @import does not work as this will scope all styles imported.
	:deep() {
		@include meta.load-css('@vuepic/vue-datepicker/dist/main.css');
	}

	// When rendered as part of NcActionInput, should be lifted above the NcPopover (99999 < 100000)
	&.vue-date-time-picker__wrapper--teleport :deep(.dp--menu-wrapper) {
		z-index: 100001;
	}

	.vue-date-time-picker--clearable :deep(.dp__input) {
		padding-inline-end: var(--default-clickable-area);
	}

	.vue-date-time-picker__timezone {
		min-width: unset;
		width: 100%;
	}

	:deep(.icon-vue) {
		// we enforce full opacity to not create a11y issues with contrast
		opacity: 1 !important;
	}

	// time selector button should have consistent padding
	:deep(.dp--tp-wrap),
	:deep(.dp__action_extra) {
		padding: var(--dp-menu-padding);
		padding-top: 0;
	}

	:deep(.dp__overlay.dp--overlay-absolute) {
		padding: var(--dp-menu-padding);

		.dp__btn.dp__button.dp__button_bottom {
			inset-block-end: 6px;
		}
	}

	:deep(.dp__btn.dp__button.dp__button_bottom),
	:deep(.dp--tp-wrap .dp__button) {
		width: 100%;
	}

	:deep(.dp__btn.dp__button.dp__overlay_action) {
		width: calc(100% - 16px);
	}

	// fix issues caused by Nextcloud server styles
	:deep(input) {
		padding-inline-start: var(--dp-input-icon-padding) !important;
	}
	:deep(.dp__btn) {
		margin: 0;
	}
	:deep(.dp__inner_nav) {
		height: fit-content;
		width: fit-content;
	}

	// make the bottom page toggle stand out better
	:deep(.dp__btn.dp__button.dp__button_bottom) {
		color: var(--color-primary-element-light-text);
		background-color: var(--color-primary-element-light);
	}

	// Fix server styles causing buttons to be primary colored
	:deep(.dp--header-wrap .dp__btn:not(.dp__button_bottom)),
	:deep(.dp__time_col .dp__btn) {
		background-color: var(--color-main-background);

		&:hover {
			background: var(--dp-hover-color);
			color: var(--dp-hover-icon-color);
		}
	}

	// Server styles cause the month and year to be fit-content -> fixing it to be max size.
	:deep(.dp__month_year_select) {
		flex: 1;
	}
	:deep(.dp--time-overlay-btn) {
		font-size: calc(2 * var(--default-font-size)) !important;
	}

	// Adjust padding to prevent horizontal scrolling in time selection
	:deep(.dp__time_input .dp__time_col_reg_block) {
		padding: 0 calc(4 * var(--default-grid-baseline));
	}

	.vue-date-time-picker.dp__theme_dark,
	.vue-date-time-picker.dp__theme_light,
	:deep(.dp__theme_dark),
	:deep(.dp__theme_light) {
		--dp-background-color: var(--color-main-background);
		--dp-text-color: var(--color-main-text);
		--dp-hover-color: var(--color-primary-element-light-hover);
		--dp-hover-text-color: var(--color-primary-element-light-text);
		--dp-hover-icon-color: var(--color-primary-element-light-text);
		--dp-primary-color: var(--color-primary-element);
		--dp-primary-disabled-color: var(--color-primary-element-hover);
		--dp-primary-text-color: var(--color-primary-element-text);
		--dp-secondary-color: var(--color-text-maxcontrast); // this is used for "disabled" dates
		--dp-border-color: var(--color-border);
		--dp-menu-border-color: var(--color-border-dark);
		--dp-border-color-hover: var(--color-border-maxcontrast);
		--dp-border-color-focus: var(--color-border-maxcontrast);
		--dp-disabled-color: var(--color-background-dark);
		--dp-disabled-color-text: var(--color-text-maxcontrast);
		--dp-scroll-bar-background: var(--color-scrollbar);
		--dp-scroll-bar-color: var(--color-scrollbar);
		--dp-success-color: var(--color-success);
		--dp-success-color-disabled: var(--color-success-hover);
		--dp-icon-color: var(--color-main-text);
		--dp-danger-color: var(--color-error);
		--dp-marker-color: var(--color-text-error, var(--color-error));
		--dp-tooltip-color: var(--color-main-text);
		--dp-highlight-color: var(--color-main-text);
	}
}
</style>
