<script lang="ts">
import { onMount, untrack } from 'svelte';
import { isoToGerman, handleGermanDateInput } from '$lib/shared/utils/date';
import { m } from '$lib/paraglide/messages.js';
import type { DatePrecision } from '$lib/shared/utils/documentDate';

/**
 * Generic date + precision input primitive shared by two domains:
 * `document/` (via WhoWhenSection) and `timeline/` (via EventForm).
 *
 * Renders three grid cells — a German `dd.mm.yyyy` text input backed by a hidden
 * ISO input, a precision <select>, and a progressively-disclosed end-date input
 * shown only for RANGE. Living in `$lib/shared/primitives/` keeps it out of either
 * consumer's domain so neither incurs a cross-domain import (eslint boundaries).
 *
 * Exposed (shared contract — both WhoWhenSection and EventForm depend on it):
 *   - dateIso, precision, endDateIso  — $bindable; the parent's binding IS the
 *     state (no redundant $state mirror).
 *   - dateInputName / endDateInputName / precisionInputName — submitted field
 *     names; defaults match the document form (`metaDatePrecision`), the timeline
 *     form overrides precisionInputName to `precision`.
 *   - initialDateIso / suggestedDateIso — seeding inputs (see onMount + $effect).
 *   - dateTestId / precisionTestId / endDateInnerTestId — forwarded data-testid
 *     attributes so existing WhoWhenSection selectors survive the extraction.
 *   - `end-date-region` is always on the OUTER aria-live wrapper of the end block.
 */
let {
	dateIso = $bindable(''),
	precision = $bindable<DatePrecision>('DAY'),
	endDateIso = $bindable(''),
	dateInputName = 'documentDate',
	endDateInputName = 'metaDateEnd',
	precisionInputName = 'metaDatePrecision',
	initialDateIso = '',
	suggestedDateIso = '',
	dateLabel = m.form_label_date(),
	dateRequired = true,
	dateError = '',
	dateTestId = undefined,
	precisionTestId = undefined,
	endDateInnerTestId = undefined
}: {
	dateIso?: string;
	precision?: DatePrecision;
	endDateIso?: string;
	dateInputName?: string;
	endDateInputName?: string;
	precisionInputName?: string;
	initialDateIso?: string;
	suggestedDateIso?: string;
	dateLabel?: string;
	dateRequired?: boolean;
	/** Server-side date error (e.g. blank required field) wired to the field's aria-invalid. */
	dateError?: string;
	dateTestId?: string;
	precisionTestId?: string;
	endDateInnerTestId?: string;
} = $props();

const PRECISIONS: { value: DatePrecision; label: () => string }[] = [
	{ value: 'DAY', label: m.date_precision_option_day },
	{ value: 'MONTH', label: m.date_precision_option_month },
	{ value: 'SEASON', label: m.date_precision_option_season },
	{ value: 'YEAR', label: m.date_precision_option_year },
	{ value: 'RANGE', label: m.date_precision_option_range },
	{ value: 'APPROX', label: m.date_precision_option_approx },
	{ value: 'UNKNOWN', label: m.date_precision_option_unknown }
];

const showEndDate = $derived(precision === 'RANGE');

// dateDisplay seeds from the bindable's value or initialDateIso once at mount
// and is then user-driven. onMount runs exactly once, so this never stomps
// the parent's dateIso on a later prop change.
let dateDisplay = $state('');
let dateDirty = $state(false);
let endDisplay = $state('');

onMount(() => {
	const seed = dateIso || initialDateIso;
	if (seed) {
		dateDisplay = isoToGerman(seed);
		if (!dateIso) dateIso = seed;
	}
	if (endDateIso) endDisplay = isoToGerman(endDateIso);
});

const dateInvalid = $derived(dateDirty && dateDisplay.length > 0 && dateIso === '');
// Either the client-side malformed-date cue or a server-provided required-field
// error marks the field invalid (REQ-011 per-field aria-invalid).
const dateFieldInvalid = $derived(dateInvalid || dateError.length > 0);

// Inline mirror of the server guard (#678). ISO YYYY-MM-DD strings compare
// lexicographically, so no Date object is needed. Server stays the gate —
// this only surfaces the error early; it never disables Save.
const endBeforeStart = $derived(
	showEndDate && endDateIso !== '' && dateIso !== '' && endDateIso < dateIso
);

function handleDateInput(e: Event) {
	const result = handleGermanDateInput(e);
	dateDisplay = result.display;
	dateIso = result.iso;
	dateDirty = true;
}

function handleEndDateInput(e: Event) {
	const result = handleGermanDateInput(e);
	endDisplay = result.display;
	endDateIso = result.iso;
}

$effect(() => {
	const suggested = suggestedDateIso;
	if (suggested && !untrack(() => dateDirty)) {
		dateDisplay = isoToGerman(suggested);
		dateIso = suggested;
	}
});
</script>

<!-- Datum (required) -->
<div data-testid={dateTestId}>
	<label for={dateInputName} class="mb-1 block text-sm font-medium text-ink-2"
		>{dateLabel}{#if dateRequired}*{/if}</label
	>
	<input
		id={dateInputName}
		type="text"
		inputmode="numeric"
		value={dateDisplay}
		oninput={handleDateInput}
		placeholder={m.form_placeholder_date()}
		maxlength="10"
		aria-required={dateRequired ? 'true' : undefined}
		class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm
               {dateFieldInvalid
			? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500'
			: 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}"
		aria-invalid={dateFieldInvalid ? 'true' : undefined}
		aria-describedby={dateFieldInvalid ? `${dateInputName}-error` : undefined}
	/>
	<input type="hidden" name={dateInputName} value={dateIso} />
	{#if dateInvalid}
		<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
			<span aria-hidden="true">⚠ </span>{m.form_date_error()}
		</p>
	{:else if dateError}
		<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
			<span aria-hidden="true">⚠ </span>{dateError}
		</p>
	{/if}
</div>

<!-- Datumsgenauigkeit (precision) -->
<div data-testid={precisionTestId}>
	<label for="{dateInputName}Precision" class="mb-1 block text-sm font-medium text-ink-2">
		{m.form_label_date_precision()}
	</label>
	<select
		id="{dateInputName}Precision"
		name={precisionInputName}
		bind:value={precision}
		class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
	>
		{#each PRECISIONS as p (p.value)}
			<option value={p.value}>{p.label()}</option>
		{/each}
	</select>
</div>

<!-- Enddatum: progressive disclosure, revealed only for RANGE, announced politely. -->
<div aria-live="polite" data-testid="end-date-region">
	{#if showEndDate}
		<div data-testid={endDateInnerTestId}>
			<label for="{dateInputName}End" class="mb-1 block text-sm font-medium text-ink-2">
				{m.form_label_date_end()}
			</label>
			<input
				id="{dateInputName}End"
				type="text"
				inputmode="numeric"
				value={endDisplay}
				oninput={handleEndDateInput}
				placeholder={m.form_placeholder_date()}
				maxlength="10"
				aria-invalid={endBeforeStart ? 'true' : undefined}
				aria-describedby={endBeforeStart ? `${dateInputName}-end-error` : undefined}
				class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm
               {endBeforeStart
					? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500'
					: 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}"
			/>
			{#if endBeforeStart}
				<!-- Non-colour cue (WCAG 1.4.1): warning glyph + text, not red alone. -->
				<p id="{dateInputName}-end-error" class="mt-1 text-xs text-danger">
					<span aria-hidden="true">⚠ </span>{m.error_invalid_date_range()}
				</p>
			{/if}
		</div>
	{/if}
</div>
<!-- Off-RANGE submits an empty string so a stale end-date never persists; the
     form action converts '' → null before sending the request body. -->
<input type="hidden" name={endDateInputName} value={showEndDate ? endDateIso : ''} />
