<script lang="ts">
	import { onMount, onDestroy } from 'svelte';
	import { slide } from 'svelte/transition';
	import { api, getBlockedBy, type BlockedByDetail , errMsg} from '$lib/api';
	import { topbarAction } from '$lib/stores/topbar-action.svelte';
	import BlockedByModal from '$lib/components/BlockedByModal.svelte';
	import { t } from '$lib/i18n';
	import { sanitizePreview } from '$lib/sanitize';
	import { templateConfigsCache, capabilitiesCache, supportedLocalesCache } from '$lib/stores/caches.svelte';
	import PageHeader from '$lib/components/PageHeader.svelte';
	import Card from '$lib/components/Card.svelte';
	import Loading from '$lib/components/Loading.svelte';
	import IconPicker from '$lib/components/IconPicker.svelte';
	import MdiIcon from '$lib/components/MdiIcon.svelte';
	import EmptyState from '$lib/components/EmptyState.svelte';
	import ConfirmModal from '$lib/components/ConfirmModal.svelte';
	import Hint from '$lib/components/Hint.svelte';
	import IconButton from '$lib/components/IconButton.svelte';
	import IconGridSelect from '$lib/components/IconGridSelect.svelte';
	import { providerTypeItems, providerTypeFilterItems, previewTargetTypeItems } from '$lib/grid-items';
	import Modal from '$lib/components/Modal.svelte';
	import JinjaEditor from '$lib/components/JinjaEditor.svelte';
	import CollapsibleSlot from '$lib/components/CollapsibleSlot.svelte';
	import EntitySelect, { type EntityItem } from '$lib/components/EntitySelect.svelte';
	import { getLocaleMeta } from '$lib/locales';
	import { snackSuccess, snackError } from '$lib/stores/snackbar.svelte';
	import { highlightFromUrl } from '$lib/highlight';
	import { globalProviderFilter } from '$lib/stores/provider-filter.svelte';
	import ErrorBanner from '$lib/components/ErrorBanner.svelte';
	import Button from '$lib/components/Button.svelte';
	import MetaStrip, { type MetaTile } from '$lib/components/MetaStrip.svelte';
	import { getDescriptor } from '$lib/providers';
	import type { TemplateConfig } from '$lib/types';

	let allTemplateConfigs = $derived(templateConfigsCache.items);
	let filterText = $state('');
	let filterType = $state('');
	let effectiveType = $derived(globalProviderFilter.providerType || filterType);
	let configs = $derived(allTemplateConfigs.filter(c =>
		(!filterText || c.name.toLowerCase().includes(filterText.toLowerCase())) &&
		(!effectiveType || c.provider_type === effectiveType)
	));
	let loaded = $state(false);
	let varsRef = $state<Record<string, any>>({});
	let showVarsFor = $state<string | null>(null);
	let showForm = $state(false);
	let editing = $state<number | null>(null);
	let error = $state('');
	let confirmDelete = $state<{ id: number; onconfirm: () => Promise<void> } | null>(null);
	/**
	 * Reset-to-default confirmation prompt. ``kind: 'slot'`` confirms a
	 * single-slot reset (slotKey populated); ``'all'`` confirms a full
	 * locale-scoped wipe. Split from confirmDelete so the two flows can
	 * coexist without stomping each other's state mid-dialog.
	 */
	let confirmReset = $state<{
		kind: 'slot' | 'all';
		slotKey?: string;
		message: string;
	} | null>(null);
	let slotPreview = $state<Record<string, string>>({});
	let slotErrors = $state<Record<string, string>>({});
	let slotErrorLines = $state<Record<string, number | null>>({});
	let slotErrorTypes = $state<Record<string, string>>({});
	let validateTimers: Record<string, ReturnType<typeof setTimeout>> = {};
	/** Clean up validate timers on unmount */
	onMount(() => {
		return () => {
			for (const timer of Object.values(validateTimers)) clearTimeout(timer);
		};
	});
	let dateFormatPreview = $state<Record<string, string | null>>({});
	let expandedSlots = $state<Set<string>>(new Set());
	let slotFilter = $state('');
	let showPreviewFor = $state<Set<string>>(new Set());

	let LOCALES = $derived(supportedLocalesCache.items);
	let primaryLocale = $derived(LOCALES[0] || 'en');
	let activeLocale = $state<string>('');
	const localeItems = $derived<EntityItem[]>(LOCALES.map((code, i) => {
		const m = getLocaleMeta(code);
		return {
			value: code,
			label: m.native,
			desc: i === 0 ? `${code.toUpperCase()} · ${t('locales.primary')}` : code.toUpperCase(),
		};
	}));
	/**
	 * Promote primary to be the active locale once the supported-locales
	 * cache loads (covers initial mount before openNew/edit ran). Without
	 * this, opening a form before fetch resolves would stay on '' / 'en'.
	 */
	$effect(() => {
		if (!activeLocale && LOCALES.length > 0) activeLocale = primaryLocale;
	});

	function toggleSlot(key: string) {
		const next = new Set(expandedSlots);
		if (next.has(key)) next.delete(key); else next.add(key);
		expandedSlots = next;
	}

	function togglePreview(key: string) {
		const next = new Set(showPreviewFor);
		if (next.has(key)) next.delete(key); else next.add(key);
		showPreviewFor = next;
	}

	function getSlotStatus(key: string): 'empty' | 'valid' | 'error' | 'warning' {
		if (slotErrors[key] && slotErrorTypes[key] !== 'undefined') return 'error';
		if (slotErrors[key] && slotErrorTypes[key] === 'undefined') return 'warning';
		if (getSlotValue(key)) return 'valid';
		return 'empty';
	}

	/** Get slot template for current locale, with fallback. */
	function getSlotValue(slotName: string): string {
		return form.slots[slotName]?.[activeLocale] || '';
	}
	/** Set slot template for current locale (immutable update). */
	function setSlotValue(slotName: string, value: string) {
		form.slots = {
			...form.slots,
			[slotName]: { ...(form.slots[slotName] || {}), [activeLocale]: value }
		};
	}

	function refreshDateFormatPreview() {
		clearTimeout(validateTimers['_dateFmt']);
		validateTimers['_dateFmt'] = setTimeout(async () => {
			try {
				const res = await api('/template-configs/preview-date-format', {
					method: 'POST',
					body: JSON.stringify({
						date_format: form.date_format,
						date_only_format: form.date_only_format,
					}),
				});
				dateFormatPreview = res;
			} catch {
				dateFormatPreview = {};
			}
		}, 400);
	}

	function validateSlot(slotKey: string, template: string, immediate = false) {
		if (validateTimers[slotKey]) clearTimeout(validateTimers[slotKey]);
		if (!template) {
			slotErrors = { ...slotErrors, [slotKey]: '' };
			slotErrorLines = { ...slotErrorLines, [slotKey]: null };
			slotErrorTypes = { ...slotErrorTypes, [slotKey]: '' };
			const { [slotKey]: _, ...rest } = slotPreview;
			slotPreview = rest;
			return;
		}

		const doValidate = async () => {
			try {
				const res = await api('/template-configs/preview-raw', { method: 'POST', body: JSON.stringify({ template, target_type: previewTargetType, date_format: form.date_format, date_only_format: form.date_only_format }) });
				slotErrors = { ...slotErrors, [slotKey]: res.error || '' };
				slotErrorLines = { ...slotErrorLines, [slotKey]: res.error_line || null };
				slotErrorTypes = { ...slotErrorTypes, [slotKey]: res.error_type || '' };
				if (res.rendered) {
					slotPreview = { ...slotPreview, [slotKey]: res.rendered };
				} else {
					const { [slotKey]: _, ...rest } = slotPreview;
					slotPreview = rest;
				}
			} catch {
				slotErrors = { ...slotErrors, [slotKey]: '' };
				slotErrorLines = { ...slotErrorLines, [slotKey]: null };
				slotErrorTypes = { ...slotErrorTypes, [slotKey]: '' };
			}
		};
		if (immediate) { doValidate(); }
		else { validateTimers[slotKey] = setTimeout(doValidate, 800); }
	}

	function refreshAllPreviews() {
		for (const group of templateSlots) {
			for (const slot of group.slots) {
				if (slot.isDateFormat) continue;
				const template = getSlotValue(slot.key);
				if (template) {
					validateSlot(slot.key, template, true);
				}
			}
		}
		refreshDateFormatPreview();
	}

	const defaultForm = () => ({
		provider_type: '', name: '', description: '', icon: '',
		slots: {} as Record<string, Record<string, string>>,
		date_format: '%d.%m.%Y, %H:%M UTC',
		date_only_format: '%d.%m.%Y',
	});
	let form = $state(defaultForm());
	let nameManuallyEdited = $state(false);
	let previewTargetType = $state('telegram');

	$effect(() => {
		if (showForm && !nameManuallyEdited && !editing) {
			const desc = getDescriptor(form.provider_type);
			form.name = desc ? `${desc.defaultName} Templates` : 'Templates';
		}
	});

	// Provider capabilities: from shared cache
	let allCapabilities = $derived(capabilitiesCache.items);
	let providerTypes = $derived(Object.keys(allCapabilities));

	// Dynamic slot definitions based on selected provider_type
	let notificationSlots = $derived<{name: string, description: string}[]>(
		allCapabilities[form.provider_type]?.notification_slots || []
	);

	// Group slots into event messages vs scheduled messages based on slot name prefix
	function slotDescription(s: {name: string, description: string}): string {
		const key = `templateSlot.${s.name}`;
		const localized = t(key);
		return localized !== key ? localized : s.description;
	}

	let templateSlots = $derived([
		{ group: 'eventMessages', slots: notificationSlots
			.filter(s => s.name.startsWith('message_'))
			.map(s => ({ key: s.name, label: s.name.replace('message_', '').replace(/_/g, ' '), description: slotDescription(s), rows: s.name === 'message_assets_added' ? 10 : 3, isDateFormat: false }))
		},
		{ group: 'scheduledMessages', slots: notificationSlots
			.filter(s => !s.name.startsWith('message_'))
			.map(s => ({ key: s.name, label: s.name.replace(/_/g, ' '), description: slotDescription(s), rows: 6, isDateFormat: false }))
		},
		{ group: 'settings', slots: [
			{ key: 'date_format', label: 'dateFormat', description: 'Date+time format', rows: 1, isDateFormat: true },
			{ key: 'date_only_format', label: 'dateOnlyFormat', description: 'Date-only format', rows: 1, isDateFormat: true },
		]},
	]);

	onMount(() => {
		topbarAction.set({
			label: t('templateConfig.newConfig'),
			onclick: () => { showForm ? (showForm = false, editing = null) : openNew(); },
		});
		load();
	});
	onDestroy(() => topbarAction.clear());

	const headerPills = $derived.by(() => {
		const pills: Array<{ label: string; tone: 'sky' }> = [];
		const types = new Set(configs.map(c => c.provider_type)).size;
		if (types > 0) pills.push({ label: `${types} ${types === 1 ? t('providers.typeSingular') : t('providers.typePlural')}`, tone: 'sky' });
		return pills;
	});

	async function load() {
		try {
			[, varsRef] = await Promise.all([
				templateConfigsCache.fetch(true),
				api('/template-configs/variables'),
				capabilitiesCache.fetch(),
				supportedLocalesCache.fetch(),
			]);
		} catch (err: unknown) { error = errMsg(err, t('common.loadError')); snackError(error); }
		finally { loaded = true; highlightFromUrl(); _openEditFromUrl(); handleDeepLink(); }
	}

	// Cross-page deep-link: ``/template-configs?edit=<id>`` auto-opens that
	// config in edit mode. Mirrors the same hook on tracking-configs so the
	// Notification Tracker form can link directly to the editor instead of
	// the generic list. Strips the param afterwards so a browser refresh
	// doesn't re-open the modal.
	function _openEditFromUrl() {
		if (typeof window === 'undefined') return;
		const params = new URLSearchParams(window.location.search);
		const editId = params.get('edit');
		if (!editId) return;
		const match = allTemplateConfigs.find(c => String(c.id) === editId);
		if (match) edit(match);
		params.delete('edit');
		const qs = params.toString();
		const cleanUrl = window.location.pathname + (qs ? '?' + qs : '');
		window.history.replaceState(null, '', cleanUrl);
	}

	/**
	 * Respond to ``?edit_slot=<slot_name>&provider=<type>`` deep-links from
	 * other pages (currently the tracking-configs Preview-template modal).
	 * Picks the first visible config matching ``provider``, opens it in edit
	 * mode, and pre-expands the target slot. Strips the param from the URL so
	 * a subsequent reload doesn't reopen the form unexpectedly.
	 */
	function handleDeepLink() {
		if (typeof window === 'undefined') return;
		const params = new URLSearchParams(window.location.search);
		const slot = params.get('edit_slot');
		if (!slot) return;
		const provider = params.get('provider') || '';
		const target = allTemplateConfigs.find(
			c => !provider || c.provider_type === provider,
		);
		// Strip the deep-link param so reload/back doesn't replay it.
		params.delete('edit_slot');
		const qs = params.toString();
		window.history.replaceState(null, '', window.location.pathname + (qs ? '?' + qs : ''));
		if (!target) {
			snackError(t('templateConfig.deepLinkNoConfig'));
			return;
		}
		edit(target);
		expandedSlots = new Set([slot]);
		// Scroll the slot into view once the form has rendered.
		setTimeout(() => {
			const el = document.getElementById(`slot-${slot}`);
			if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
		}, 200);
	}

	function openNew() {
		form = defaultForm();
		if (providerTypes.length > 0) form.provider_type = providerTypes[0];
		nameManuallyEdited = false;
		editing = null; showForm = true; activeLocale = primaryLocale; slotPreview = {}; slotErrors = {}; dateFormatPreview = {}; expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = '';
		refreshDateFormatPreview();
	}
	function edit(c: TemplateConfig) {
		form = {
			provider_type: c.provider_type,
			name: c.name,
			description: c.description || '',
			icon: c.icon || '',
			slots: Object.fromEntries(Object.entries(c.slots).map(([k, v]) => [k, { ...v }])),
			date_format: c.date_format || '%d.%m.%Y, %H:%M UTC',
			date_only_format: c.date_only_format || '%d.%m.%Y',
		};
		nameManuallyEdited = true;
		editing = c.id; showForm = true; activeLocale = primaryLocale;
		slotPreview = {}; slotErrors = {}; dateFormatPreview = {};
		expandedSlots = new Set(); showPreviewFor = new Set(); slotFilter = '';
		setTimeout(() => refreshAllPreviews(), 100);
	}

	async function save(e: SubmitEvent) {
		e.preventDefault(); error = '';
		try {
			if (editing) await api(`/template-configs/${editing}`, { method: 'PUT', body: JSON.stringify(form) });
			else await api('/template-configs', { method: 'POST', body: JSON.stringify(form) });
			showForm = false; editing = null; await load();
			snackSuccess(t('snack.templateSaved'));
		} catch (err: unknown) { const __m = errMsg(err); error = __m; snackError(__m); }
	}

	/**
	 * Ask the user to confirm a reset. The actual fetch+replace runs in
	 * ``performReset`` after the ConfirmModal's onconfirm fires. Split into
	 * two steps so we can use the app-wide ConfirmModal (consistent look,
	 * keyboard handling) instead of ``window.confirm`` (blocks the page).
	 */
	function resetSlotToDefault(slotKey: string) {
		if (!form.provider_type) return;
		confirmReset = {
			kind: 'slot',
			slotKey,
			message: t('templateConfig.resetSlotConfirm').replace('{locale}', activeLocale.toUpperCase()),
		};
	}

	function resetAllToDefaults() {
		if (!form.provider_type) return;
		confirmReset = {
			kind: 'all',
			message: t('templateConfig.resetAllConfirm').replace(/\{locale\}/g, activeLocale.toUpperCase()),
		};
	}

	async function performReset() {
		if (!confirmReset || !form.provider_type) return;
		const { kind, slotKey } = confirmReset;
		confirmReset = null;
		try {
			if (kind === 'slot' && slotKey) {
				const res = await api<Record<string, Record<string, string>>>(
					`/template-configs/defaults?provider_type=${encodeURIComponent(form.provider_type)}&slot_name=${encodeURIComponent(slotKey)}&locale=${encodeURIComponent(activeLocale)}`,
				);
				const text = res?.[slotKey]?.[activeLocale];
				if (!text) {
					snackError(t('templateConfig.resetNoDefault'));
					return;
				}
				setSlotValue(slotKey, text);
				validateSlot(slotKey, text, true);
			} else {
				const res = await api<Record<string, Record<string, string>>>(
					`/template-configs/defaults?provider_type=${encodeURIComponent(form.provider_type)}&locale=${encodeURIComponent(activeLocale)}`,
				);
				// Replace current-locale slots; leave other locales' values untouched.
				const nextSlots = { ...form.slots };
				for (const [key, localeMap] of Object.entries(res || {})) {
					const text = localeMap?.[activeLocale];
					if (text === undefined) continue;
					nextSlots[key] = { ...(nextSlots[key] || {}), [activeLocale]: text };
				}
				form.slots = nextSlots;
				refreshAllPreviews();
			}
			snackSuccess(t('templateConfig.resetApplied'));
		} catch (err: unknown) {
			snackError(errMsg(err));
		}
	}

	function clone(c: TemplateConfig) {
		form = {
			provider_type: c.provider_type,
			name: `${c.name} (Copy)`,
			description: c.description || '',
			icon: c.icon || '',
			slots: Object.fromEntries(Object.entries(c.slots).map(([k, v]) => [k, { ...v }])),
			date_format: c.date_format || '%d.%m.%Y, %H:%M UTC',
			date_only_format: c.date_only_format || '%d.%m.%Y',
		};
		editing = null;
		showForm = true;
		activeLocale = primaryLocale;
		slotPreview = {};
		slotErrors = {};
		setTimeout(() => refreshAllPreviews(), 100);
	}

	function templateConfigTiles(config: TemplateConfig): MetaTile[] {
		const tiles: MetaTile[] = [];
		tiles.push({
			icon: 'mdiServer',
			label: config.provider_type,
			tone: 'lavender',
			mono: true,
		});
		const slotCount = Object.keys(config.slots || {}).length;
		tiles.push({
			icon: 'mdiViewGridOutline',
			value: String(slotCount),
			label: t('templateConfig.slots'),
			tone: slotCount > 0 ? 'sky' : 'default',
		});
		// Locale coverage — count unique locales present across all slots
		const locales = new Set<string>();
		for (const s of Object.values(config.slots || {})) {
			for (const loc of Object.keys(s || {})) locales.add(loc);
		}
		if (locales.size > 0) {
			tiles.push({
				icon: 'mdiTranslate',
				value: String(locales.size),
				label: locales.size === 1 ? t('locales.label') : t('locales.labelPlural'),
				hint: [...locales].sort().join(', '),
				tone: 'mint',
			});
		}
		if (config.user_id === 0) {
			tiles.push({
				icon: 'mdiShieldStarOutline',
				label: t('common.system'),
				tone: 'orchid',
			});
		}
		return tiles;
	}

	let blockedBy = $state<BlockedByDetail | null>(null);
	function remove(id: number) {
		confirmDelete = {
			id,
			onconfirm: async () => {
				try { await api(`/template-configs/${id}`, { method: 'DELETE' }); await load(); snackSuccess(t('snack.templateDeleted')); }
				catch (err: unknown) {
					const bb = getBlockedBy(err);
					if (bb) { blockedBy = bb; return; }
					const m = errMsg(err); error = m; snackError(m);
				}
				finally { confirmDelete = null; }
			}
		};
	}
</script>

<PageHeader
	title={t('templateConfig.title')}
	emphasis={t('templateConfig.titleEmphasis')}
	description={t('templateConfig.description')}
	crumb={t('crumbs.routingNotification')}
	count={configs.length}
	countLabel={t('templateConfig.countLabel')}
	pills={headerPills}
>
	<Button size="sm" onclick={() => { showForm ? (showForm = false, editing = null) : openNew(); }}>
		{showForm ? t('common.cancel') : t('templateConfig.newConfig')}
	</Button>
</PageHeader>

{#if !loaded}<Loading />{:else}

{#if showForm}
<div in:slide={{ duration: 200 }}>
<Card class="mb-6">
	{#if error}<ErrorBanner message={error} />{/if}
	<form onsubmit={save} class="space-y-5">
		<div>
			<label for="tpc-name" class="block text-sm font-medium mb-1">{t('templateConfig.name')}</label>
			<div class="flex gap-2">
				<IconPicker value={form.icon} onselect={(v: string) => form.icon = v} />
				<input id="tpc-name" bind:value={form.name} oninput={() => nameManuallyEdited = true} required placeholder={t('templateConfig.namePlaceholder')}
					class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
			</div>
		</div>
		<div>
			<label for="tpc-desc" class="block text-sm font-medium mb-1">{t('common.description')}</label>
			<input id="tpc-desc" bind:value={form.description} placeholder={t('templateConfig.descriptionPlaceholder')}
				class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
		</div>

		{#if !editing}
		<div>
			<div class="block text-sm font-medium mb-1">{t('templateConfig.providerType')}</div>
			<IconGridSelect items={providerTypeItems()} bind:value={form.provider_type} columns={2} />
		</div>
		{:else}
		<div>
			<span class="block text-sm font-medium mb-1">{t('templateConfig.providerType')}</span>
			<span class="text-sm text-[var(--color-muted-foreground)]">{allCapabilities[form.provider_type]?.display_name || form.provider_type}</span>
		</div>
		{/if}

		<div class="flex items-center gap-2">
			<span class="text-sm font-medium">{t('templateConfig.previewAs')}:</span>
			<IconGridSelect items={previewTargetTypeItems()} bind:value={previewTargetType} columns={2} />
		</div>

		<!-- Language picker -->
		<div class="flex items-center gap-2 mb-3">
			<span class="text-xs font-medium text-[var(--color-muted-foreground)] shrink-0">
				{t('templateConfig.language')}
			</span>
			<div class="flex-1 max-w-xs">
				<EntitySelect
					items={localeItems}
					value={activeLocale}
					size="sm"
					onselect={(v) => { activeLocale = (v as string) || primaryLocale; refreshAllPreviews(); }}
				/>
			</div>
			{#if form.provider_type}
				<button type="button" onclick={resetAllToDefaults}
					title={t('templateConfig.resetAllToDefaults')}
					class="ml-auto flex items-center gap-1 text-xs px-2 py-1 rounded-md border border-[var(--color-border)] text-[var(--color-muted-foreground)] hover:bg-[var(--color-muted)]">
					<MdiIcon name="mdiRefresh" size={12} />
					{t('templateConfig.resetAllToDefaults')}
				</button>
			{/if}
		</div>

		<!-- Slot filter -->
		{#if notificationSlots.length > 4}
		<div>
			<input type="text" bind:value={slotFilter} placeholder={t('templateConfig.filterSlots')}
				class="w-full px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
		</div>
		{/if}

		{#each templateSlots.filter(g => g.slots.length > 0) as group}
			{@const filteredSlots = slotFilter ? group.slots.filter(s => (s.description || s.label).toLowerCase().includes(slotFilter.toLowerCase())) : group.slots}
			{#if filteredSlots.length > 0}
			<fieldset class="border border-[var(--color-border)] rounded-md p-3">
				<legend class="text-sm font-medium px-1">{t(`templateConfig.${group.group}`)}{#if group.group === 'eventMessages'}<Hint text={t('hints.eventMessages')} />{:else if group.group === 'scheduledMessages'}<Hint text={t('hints.scheduledMessages')} />{/if}</legend>
				<div class="space-y-2 mt-2">
					{#each filteredSlots as slot}
						{#if slot.isDateFormat}
							<div>
								<div class="flex items-center justify-between mb-1">
									<label for="datefmt-{slot.key}" class="text-xs text-[var(--color-muted-foreground)]">{slot.description || t(`templateConfig.${slot.label}`, slot.label)}</label>
								</div>
								<input id="datefmt-{slot.key}" value={(form as any)[slot.key]}
									oninput={(e: Event) => { (form as any)[slot.key] = (e.target as HTMLInputElement).value; clearTimeout(validateTimers['_fmt']); validateTimers['_fmt'] = setTimeout(refreshAllPreviews, 600); refreshDateFormatPreview(); }}
									class="w-full px-2 py-1 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)] font-mono" />
								{#if dateFormatPreview[slot.key]}
									<p class="mt-1 text-xs font-mono" style="color: var(--color-muted-foreground);">{t('templateConfig.preview')}: <span style="color: var(--color-foreground);">{dateFormatPreview[slot.key]}</span></p>
								{:else if dateFormatPreview[slot.key] === null}
									<p class="mt-1 text-xs" style="color: var(--color-error-fg);">{t('templateConfig.invalidFormat')}</p>
								{/if}
							</div>
						{:else}
							<div id="slot-{slot.key}">
							<CollapsibleSlot
								label={slot.key}
								description={slot.description || t(`templateConfig.${slot.label}`, slot.label)}
								expanded={expandedSlots.has(slot.key)}
								status={getSlotStatus(slot.key)}
								ontoggle={() => toggleSlot(slot.key)}
							>
								<div class="flex items-center justify-end gap-2 mb-2">
									{#if slotPreview[slot.key] && !slotErrors[slot.key]}
										<button type="button" onclick={() => togglePreview(slot.key)}
											class="text-xs px-2 py-0.5 rounded-md transition-colors {showPreviewFor.has(slot.key) ? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]' : 'text-[var(--color-muted-foreground)] hover:bg-[var(--color-muted)]'}">
											{t('templateConfig.preview')}
										</button>
									{/if}
									{#if varsRef[slot.key]}
										<button type="button" onclick={() => showVarsFor = slot.key}
											class="text-xs text-[var(--color-muted-foreground)] hover:underline">{t('templateConfig.variables')}</button>
									{/if}
									<button type="button" onclick={() => resetSlotToDefault(slot.key)}
										title={t('templateConfig.resetToDefault')}
										class="text-xs text-[var(--color-muted-foreground)] hover:underline">
										{t('templateConfig.resetToDefault')}
									</button>
								</div>

								{#if showPreviewFor.has(slot.key) && slotPreview[slot.key] && !slotErrors[slot.key]}
									<div class="p-2 bg-[var(--color-muted)] rounded text-sm preview-html mb-2">
										<pre class="whitespace-pre-wrap text-xs">{@html sanitizePreview(slotPreview[slot.key])}</pre>
									</div>
								{:else}
									<JinjaEditor value={getSlotValue(slot.key)} onchange={(v: string) => { setSlotValue(slot.key, v); validateSlot(slot.key, v); }} rows={slot.rows || 3} errorLine={slotErrorLines[slot.key] || null} variables={varsRef[slot.key] || undefined} />
								{/if}

								{#if slotErrors[slot.key]}
									{#if slotErrorTypes[slot.key] === 'undefined'}
										<p class="mt-1 text-xs" style="color: var(--color-warning-fg);">⚠ {t('common.undefinedVar')}: {slotErrors[slot.key]}</p>
									{:else}
										<p class="mt-1 text-xs" style="color: var(--color-error-fg);">РІСљвЂў {t('common.syntaxError')}: {slotErrors[slot.key]}{slotErrorLines[slot.key] ? ` (${t('common.line')} ${slotErrorLines[slot.key]})` : ''}</p>
									{/if}
								{/if}
							</CollapsibleSlot>
							</div>
						{/if}
					{/each}
				</div>
			</fieldset>
			{/if}
		{/each}

		<Button type="submit">
			{editing ? t('common.save') : t('common.create')}
		</Button>
	</form>
</Card>
</div>
{/if}

{#if !showForm && allTemplateConfigs.length > 0}
<div class="flex items-center gap-2 mb-3">
	<input type="text" bind:value={filterText} placeholder={t('common.filterByName')}
		class="flex-1 px-3 py-1.5 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
	{#if !globalProviderFilter.id}
	<div class="w-48">
		<IconGridSelect items={providerTypeFilterItems()} bind:value={filterType} columns={2} compact />
	</div>
	{/if}
</div>
{/if}

{#if allTemplateConfigs.length === 0 && !showForm}
	<Card>
		<EmptyState icon="mdiFileDocumentEdit" message={t('templateConfig.noConfigs')} />
	</Card>
{:else if configs.length === 0 && !showForm}
	<Card>
		<EmptyState icon="mdiFilterOff" message={t('common.noFilterResults')} />
	</Card>
{:else}
	<div class="list-stack stagger-children">
		{#each configs as config}
			<Card hover entityId={config.id}>
				<div class="list-row">
					<div class="list-row__identity">
						<div class="flex items-center gap-2 min-w-0">
							<span style="color: var(--color-primary);" class="shrink-0"><MdiIcon name={config.icon || 'mdiFileDocumentEdit'} size={20} /></span>
							<p class="font-medium truncate">{config.name}</p>
							<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)] shrink-0">{config.provider_type}</span>
							{#if config.user_id === 0}
								<span class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)] shrink-0">{t('common.system')}</span>
							{/if}
						</div>
						{#if config.description}
							<p class="text-sm text-[var(--color-muted-foreground)] mt-1 list-row__secondary">{config.description}</p>
						{/if}
					</div>
					<MetaStrip tiles={templateConfigTiles(config)} />
					<div class="list-row__actions">
						<IconButton icon="mdiContentCopy" title={t('common.clone')} onclick={() => clone(config)} />
						<IconButton icon="mdiPencil" title={t('common.edit')} onclick={() => edit(config)} />
						<IconButton icon="mdiDelete" title={t('common.delete')} onclick={() => remove(config.id)} variant="danger" />
					</div>
				</div>
			</Card>
		{/each}
	</div>
{/if}

{/if}

<ConfirmModal open={confirmDelete !== null} message={t('templateConfig.confirmDelete')}
	onconfirm={() => confirmDelete?.onconfirm()} oncancel={() => confirmDelete = null} />

<ConfirmModal open={confirmReset !== null}
	title={t('templateConfig.resetToDefault')}
	message={confirmReset?.message || ''}
	confirmLabel={confirmReset?.kind === 'all' ? t('templateConfig.resetAllToDefaults') : t('templateConfig.resetToDefault')}
	confirmIcon="mdiRefresh"
	onconfirm={performReset}
	oncancel={() => confirmReset = null} />

<BlockedByModal open={!!blockedBy} detail={blockedBy} onclose={() => blockedBy = null} />

<!-- Variables reference modal -->
<Modal open={showVarsFor !== null} title="{t('templateConfig.variables')}: {showVarsFor ? t(`templateConfig.${templateSlots.flatMap(g => g.slots).find(s => s.key === showVarsFor)?.label || showVarsFor}`) : ''}" onclose={() => showVarsFor = null}>
	{#if showVarsFor && varsRef[showVarsFor]}
		<p class="text-sm text-[var(--color-muted-foreground)] mb-3">{t(`templateVars.${showVarsFor}.description`, varsRef[showVarsFor].description)}</p>
		<div class="space-y-1">
			<p class="text-xs font-medium mb-1">{t('templateConfig.variables')}:</p>
			{#each Object.entries(varsRef[showVarsFor].variables || {}) as [name, desc]}
				<div class="flex items-start gap-2 text-sm">
					<code class="text-xs bg-[var(--color-muted)] px-1 py-0.5 rounded font-mono whitespace-nowrap">{'{{ ' + name + ' }}'}</code>
					<span class="text-xs text-[var(--color-muted-foreground)]">{t(`templateVars.${name}`, desc as string)}</span>
				</div>
			{/each}
		</div>
		{#if varsRef[showVarsFor].asset_fields && typeof varsRef[showVarsFor].asset_fields === 'object'}
			<div class="mt-3 pt-3 border-t border-[var(--color-border)]">
				<p class="text-xs font-medium mb-1">{t('templateConfig.assetFields')}:</p>
				{#each Object.entries(varsRef[showVarsFor].asset_fields) as [name, desc]}
					<div class="flex items-start gap-2 text-sm">
						<code class="text-xs bg-[var(--color-muted)] px-1 py-0.5 rounded font-mono whitespace-nowrap">{'{{ asset.' + name + ' }}'}</code>
						<span class="text-xs text-[var(--color-muted-foreground)]">{t(`templateVars.asset_${name}`, desc as string)}</span>
					</div>
				{/each}
			</div>
		{/if}
		{#if varsRef[showVarsFor].album_fields && typeof varsRef[showVarsFor].album_fields === 'object'}
			<div class="mt-3 pt-3 border-t border-[var(--color-border)]">
				<p class="text-xs font-medium mb-1">{t('templateConfig.albumFields')}:</p>
				{#each Object.entries(varsRef[showVarsFor].album_fields) as [name, desc]}
					<div class="flex items-start gap-2 text-sm">
						<code class="text-xs bg-[var(--color-muted)] px-1 py-0.5 rounded font-mono whitespace-nowrap">{'{{ album.' + name + ' }}'}</code>
						<span class="text-xs text-[var(--color-muted-foreground)]">{t(`templateVars.album_${name}`, desc as string)}</span>
					</div>
				{/each}
			</div>
		{/if}
	{/if}
</Modal>

<style>
	:global(.preview-html a) {
		color: var(--color-primary);
		text-decoration: underline;
	}
	:global(.preview-html a:hover) {
		opacity: 0.8;
	}
</style>
