<script lang="ts">
import { tick } from 'svelte';
import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
import { formatDocumentMetaLine } from './utils';

type JourneyItemView = components['schemas']['JourneyItemView'];

interface Props {
	item: JourneyItemView;
	index: number;
	total: number;
	pendingRemove?: boolean;
	onMoveUp: () => void;
	onMoveDown: () => void;
	onRemove: () => void;
	onNotePatch: (note: string | null) => Promise<void>;
}

let {
	item,
	index,
	total,
	pendingRemove = false,
	onMoveUp,
	onMoveDown,
	onRemove,
	onNotePatch
}: Props = $props();

const isInterlude = $derived(!item.document);
const itemTitle = $derived(item.document?.title ?? m.journey_interlude_label());
// Spec LE-2 "Briefmeta": date · von X an Y disambiguates near-identical titles.
const metaLine = $derived(item.document ? formatDocumentMetaLine(item.document) : '');
const needsConfirmOnRemove = $derived(!!item.note);

let rootEl: HTMLElement | null = $state(null);
let showNote = $state(!!item.note);
let noteDraft = $state(item.note ?? '');
let noteSaving = $state(false);
let noteError = $state('');
let showRemoveConfirm = $state(false);

async function handleNoteBlur() {
	if (noteSaving) return;
	const normalizedDraft = noteDraft.trim().length === 0 ? null : noteDraft;
	// '' and undefined both mean "no note" — never PATCH a no-op.
	if (normalizedDraft === (item.note ?? null)) {
		// Opened "Notiz hinzufügen" and blurred without typing → collapse again.
		if (!isInterlude && normalizedDraft === null) showNote = false;
		return;
	}
	if (isInterlude && normalizedDraft === null) {
		// Interludes must keep a note — restore the draft so the UI doesn't show
		// an emptied text that the server still holds.
		noteDraft = item.note ?? '';
		return;
	}

	noteSaving = true;
	noteError = '';
	try {
		await onNotePatch(normalizedDraft);
		// Clearing an existing note collapses the textarea after the PATCH lands.
		if (normalizedDraft === null) showNote = false;
	} catch (e) {
		noteError = e instanceof Error && e.message ? e.message : m.journey_note_error();
	} finally {
		noteSaving = false;
	}
}

async function handleNoteRemove() {
	const prevDraft = noteDraft;
	const prevShowNote = showNote;
	noteDraft = '';
	showNote = false;
	noteError = '';
	try {
		await onNotePatch(null);
	} catch (e) {
		noteDraft = prevDraft;
		showNote = prevShowNote;
		noteError = e instanceof Error && e.message ? e.message : m.journey_note_error();
	}
}

async function handleNoteOpen() {
	showNote = true;
	// Spec LE-3: focus moves into the revealed textarea.
	await tick();
	rootEl?.querySelector<HTMLTextAreaElement>('textarea')?.focus();
}

async function handleRemoveClick() {
	if (needsConfirmOnRemove) {
		showRemoveConfirm = true;
		await tick();
		rootEl?.querySelector<HTMLElement>('[data-remove-confirm-cancel]')?.focus();
	} else {
		onRemove();
	}
}

function handleRemoveConfirm() {
	showRemoveConfirm = false;
	onRemove();
}

async function handleRemoveCancel() {
	showRemoveConfirm = false;
	await tick();
	rootEl?.querySelector<HTMLElement>('[data-remove-btn]')?.focus();
}
</script>

<div
	bind:this={rootEl}
	data-block-id={item.id}
	class={[
		'flex min-w-0 flex-col rounded border transition-colors',
		pendingRemove ? 'opacity-60' : '',
		isInterlude
			? 'border-l-4 border-line border-l-interlude-border bg-interlude-bg'
			: 'border-line bg-surface'
	].join(' ')}
>
	<div class="flex min-w-0 items-center gap-1 px-2 py-1">
		<!-- Drag handle (desktop, pointer-only — keyboard users reorder via the move buttons) -->
		<button
			type="button"
			data-drag-handle
			tabindex="-1"
			aria-hidden="true"
			class="hidden shrink-0 cursor-grab items-center justify-center self-center text-ink-3 transition-colors hover:text-ink active:cursor-grabbing md:flex"
			style="min-height: 44px; min-width: 44px;"
		>
			⠿
		</button>

		<!-- Move up/down (mobile + always visible) -->
		<div class="flex shrink-0 flex-col self-start">
			<button
				type="button"
				data-move-up
				onclick={onMoveUp}
				disabled={index === 0}
				aria-label={m.journey_move_up({ title: itemTitle })}
				class="flex min-h-[44px] min-w-[44px] items-center justify-center rounded text-ink-3 transition-colors hover:bg-muted hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:opacity-20"
			>
				<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
					<path stroke-linecap="round" stroke-linejoin="round" d="M5 15l7-7 7 7" />
				</svg>
			</button>
			<button
				type="button"
				onclick={onMoveDown}
				disabled={index === total - 1}
				aria-label={m.journey_move_down({ title: itemTitle })}
				class="flex min-h-[44px] min-w-[44px] items-center justify-center rounded text-ink-3 transition-colors hover:bg-muted hover:text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:opacity-20"
			>
				<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
					<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
				</svg>
			</button>
		</div>

		<!-- Content (title + note inline) -->
		<div class="min-w-0 flex-1 py-1 break-words">
			{#if isInterlude}
				<span class="font-sans text-xs font-bold tracking-widest text-interlude-label uppercase">
					{m.journey_interlude_label()}
				</span>
			{:else}
				<span class="font-sans text-xs text-ink-3">{index + 1}.</span>
				<span class="ml-1 font-serif text-sm text-ink">{item.document!.title}</span>
				{#if metaLine}
					<p class="mt-0.5 font-sans text-xs text-ink-3">{metaLine}</p>
				{/if}
			{/if}

			{#if showNote}
				<div class="mt-2">
					<textarea
						aria-label={m.journey_note_aria_label({ title: itemTitle })}
						bind:value={noteDraft}
						onblur={handleNoteBlur}
						maxlength={2000}
						rows={2}
						class="block w-full resize-y rounded border border-line bg-transparent px-2 py-1.5 font-sans text-sm text-ink placeholder:text-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
					></textarea>
					<div class="mt-1 flex items-center justify-between gap-2">
						<p class="font-sans text-xs text-ink-3">{m.journey_note_save_hint()}</p>
						{#if !isInterlude}
							<button
								type="button"
								onclick={handleNoteRemove}
								class="inline-flex min-h-[44px] items-center font-sans text-xs text-ink-3 underline hover:text-danger focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
							>
								{m.journey_note_remove()}
							</button>
						{/if}
					</div>
					{#if noteError}
						<p class="mt-1 font-sans text-xs text-danger" role="alert">{noteError}</p>
					{/if}
				</div>
			{:else if !isInterlude}
				<button
					type="button"
					onclick={handleNoteOpen}
					aria-expanded={showNote}
					class="mt-0.5 inline-flex min-h-[44px] items-center font-sans text-xs text-ink-3 underline hover:text-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
				>
					{m.journey_note_add()}
				</button>
			{/if}
		</div>

		<!-- Remove button / confirm / pending -->
		<div class="shrink-0 self-start">
			{#if pendingRemove}
				<span class="inline-flex min-h-[44px] items-center font-sans text-xs text-ink-3 italic">
					{m.journey_item_pending_remove()}
				</span>
			{:else if showRemoveConfirm}
				<div role="group" aria-label={m.journey_remove_confirm()} class="flex items-center gap-2">
					<span class="font-sans text-xs text-ink-2">{m.journey_remove_confirm()}</span>
					<button
						type="button"
						onclick={handleRemoveConfirm}
						onkeydown={(e) => e.key === 'Escape' && handleRemoveCancel()}
						class="inline-flex min-h-[44px] items-center rounded bg-danger px-3 font-sans text-xs font-medium text-white hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
					>
						{m.journey_remove_confirm_yes()}
					</button>
					<button
						type="button"
						data-remove-confirm-cancel
						onclick={handleRemoveCancel}
						onkeydown={(e) => e.key === 'Escape' && handleRemoveCancel()}
						class="inline-flex min-h-[44px] items-center rounded border border-line px-3 font-sans text-xs font-medium text-ink hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
					>
						{m.journey_remove_confirm_cancel()}
					</button>
				</div>
			{:else}
				<button
					type="button"
					data-remove-btn
					onclick={handleRemoveClick}
					aria-label={m.journey_remove_item_aria({ title: itemTitle })}
					class="-m-1 inline-flex min-h-[44px] min-w-[44px] items-center justify-center rounded p-3 text-ink-3 hover:text-danger focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
				>
					<svg
						class="h-4 w-4"
						fill="none"
						viewBox="0 0 24 24"
						stroke="currentColor"
						stroke-width="2"
					>
						<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
					</svg>
				</button>
			{/if}
		</div>
	</div>
</div>
