<script lang="ts">
	import { slide, fade } from 'svelte/transition';
	import { flip } from 'svelte/animate';
	import { api, errMsg, getBlockedBy, type BlockedByDetail } from '$lib/api';
	import BlockedByModal from '$lib/components/BlockedByModal.svelte';
	import { t, getLocale } from '$lib/i18n';
	import { telegramBotsCache } from '$lib/stores/caches.svelte';
	import PageHeader from '$lib/components/PageHeader.svelte';
	import Card from '$lib/components/Card.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 IconButton from '$lib/components/IconButton.svelte';
	import EntitySelect from '$lib/components/EntitySelect.svelte';
	import { snackSuccess, snackError, snackInfo } from '$lib/stores/snackbar.svelte';
	import Button from '$lib/components/Button.svelte';
	import ErrorBanner from '$lib/components/ErrorBanner.svelte';
	import MetaStrip, { type MetaTile } from '$lib/components/MetaStrip.svelte';
	import type { TelegramBot, TelegramChat } from '$lib/types';

	interface CommandTrackerSummary { id: number; name: string; icon?: string; enabled: boolean }
	interface ListenerEntry { listener_type: string; listener_id: number }
	interface WebhookStatusInfo { url?: string; pending_update_count?: number; last_error_message?: string }
	interface ApiResult { success: boolean; error?: string; verified?: boolean }

	let { settings, onreload }: { settings: Record<string, string>; onreload: () => Promise<void> } = $props();

	let bots = $derived(telegramBotsCache.items);
	let showForm = $state(false);
	let editing = $state<number | null>(null);
	let form = $state({ name: '', icon: '', token: '' });
	let nameManuallyEdited = $state(false);
	let error = $state('');
	let submitting = $state(false);
	let confirmDelete = $state<{ id: number; onconfirm: () => Promise<void> } | null>(null);

	const DEFAULT_BOT_NAME = 'Telegram Bot';
	$effect(() => {
		if (showForm && !nameManuallyEdited && !editing) {
			form.name = DEFAULT_BOT_NAME;
		}
	});

	// Per-bot expandable sections
	let chats = $state<Record<number, TelegramChat[]>>({});
	let chatsLoading = $state<Record<number, boolean>>({});
	// Distinct from chatsLoading: refresh keeps the existing list visible
	// instead of swapping it for a placeholder, avoiding the disorienting
	// "everything disappears" flash during Discover.
	let chatsRefreshing = $state<Record<number, boolean>>({});
	let expandedSection = $state<Record<number, string>>({});

	// Webhook status per bot
	let webhookStatus = $state<Record<number, WebhookStatusInfo>>({});

	let chatTesting = $state<Record<string, boolean>>({});
	let modeChanging = $state<Record<number, boolean>>({});

	// Listener status: command trackers using this bot
	let botListenerStatus = $state<Record<number, CommandTrackerSummary[]>>({});
	let botListenerLoading = $state<Record<number, boolean>>({});

	function telegramBotTiles(bot: TelegramBot): MetaTile[] {
		const tiles: MetaTile[] = [];
		const mode = bot.update_mode || 'none';
		const modeTone: MetaTile['tone'] = mode === 'webhook' ? 'lavender' : mode === 'polling' ? 'mint' : 'default';
		const modeLabel = mode === 'webhook' ? t('telegramBot.webhook') : mode === 'polling' ? t('telegramBot.polling') : t('telegramBot.none');
		tiles.push({
			icon: mode === 'webhook' ? 'mdiWebhook' : mode === 'polling' ? 'mdiSync' : 'mdiPowerOff',
			label: modeLabel,
			tone: modeTone,
		});
		if (bot.bot_username) {
			tiles.push({
				icon: 'mdiAt',
				label: bot.bot_username,
				tone: 'sky',
				mono: true,
			});
		}
		const chatCount = chats[bot.id]?.length;
		if (chatCount !== undefined) {
			tiles.push({
				icon: 'mdiChat',
				value: String(chatCount),
				label: t('telegramBot.chats'),
				tone: chatCount > 0 ? 'orchid' : 'default',
			});
		}
		return tiles;
	}

	function openNew() { form = { name: '', icon: '', token: '' }; nameManuallyEdited = false; editing = null; showForm = true; }
	function editBot(bot: TelegramBot) { form = { name: bot.name, icon: bot.icon || '', token: '' }; nameManuallyEdited = true; editing = bot.id; showForm = true; }

	async function saveBot(e: SubmitEvent) {
		e.preventDefault(); error = ''; submitting = true;
		try {
			if (editing) {
				await api(`/telegram-bots/${editing}`, { method: 'PUT', body: JSON.stringify({ name: form.name, icon: form.icon }) });
				snackSuccess(t('snack.botUpdated'));
			} else {
				await api('/telegram-bots', { method: 'POST', body: JSON.stringify(form) });
				snackSuccess(t('snack.botRegistered'));
			}
			form = { name: '', icon: '', token: '' }; nameManuallyEdited = false; showForm = false; editing = null; await onreload();
		} catch (err: unknown) { const m = errMsg(err); error = m; snackError(m); }
		finally { submitting = false; }
	}

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

	async function toggleSection(botId: number, section: string) {
		if (expandedSection[botId] === section) {
			expandedSection = { ...expandedSection, [botId]: '' };
			return;
		}
		if (section === 'chats' && !chats[botId]) await loadChats(botId);
		else if (section === 'listeners' && !botListenerStatus[botId]) await loadListenerStatus(botId);
		expandedSection = { ...expandedSection, [botId]: section };
	}

	async function loadChats(botId: number) {
		chatsLoading = { ...chatsLoading, [botId]: true };
		try { chats = { ...chats, [botId]: await api<TelegramChat[]>(`/telegram-bots/${botId}/chats`) }; } catch (e) { console.warn('Failed to load chats:', e); chats = { ...chats, [botId]: [] }; }
		chatsLoading = { ...chatsLoading, [botId]: false };
	}

	async function discoverChats(botId: number) {
		if (chatsRefreshing[botId]) return;
		chatsRefreshing = { ...chatsRefreshing, [botId]: true };
		try {
			chats = { ...chats, [botId]: await api<TelegramChat[]>(`/telegram-bots/${botId}/chats/discover`, { method: 'POST' }) };
			snackSuccess(t('telegramBot.chatsDiscovered'));
		} catch (err: unknown) { snackError(errMsg(err)); }
		chatsRefreshing = { ...chatsRefreshing, [botId]: false };
	}

	async function deleteChat(botId: number, chatDbId: number) {
		try {
			await api(`/telegram-bots/${botId}/chats/${chatDbId}`, { method: 'DELETE' });
			chats[botId] = (chats[botId] || []).filter((c) => c.id !== chatDbId);
			snackSuccess(t('telegramBot.chatDeleted'));
		} catch (err: unknown) { snackError(errMsg(err)); }
	}

	// `desc` is the only locale-sensitive field — language *names* are intentionally
	// shown in their own language (English under EN, Русский under RU, …) so users
	// recognise them regardless of the current UI locale. Only the "Auto" sentinel
	// for the no-override row is translated.
	let LANG_ITEMS = $derived([
		{ value: '', label: '—', icon: 'mdiTranslate', desc: t('common.auto') },
		{ value: 'en', label: 'EN', icon: 'mdiAlphaECircle', desc: 'English' },
		{ value: 'ru', label: 'RU', icon: 'mdiAlphaRCircle', desc: 'Русский' },
		{ value: 'uk', label: 'UK', icon: 'mdiAlphaUCircle', desc: 'Українська' },
		{ value: 'de', label: 'DE', icon: 'mdiAlphaDCircle', desc: 'Deutsch' },
		{ value: 'fr', label: 'FR', icon: 'mdiAlphaFCircle', desc: 'Français' },
		{ value: 'es', label: 'ES', icon: 'mdiAlphaECircle', desc: 'Español' },
		{ value: 'it', label: 'IT', icon: 'mdiAlphaICircle', desc: 'Italiano' },
		{ value: 'pt', label: 'PT', icon: 'mdiAlphaPCircle', desc: 'Português' },
		{ value: 'zh', label: 'ZH', icon: 'mdiAlphaZCircle', desc: '中文' },
		{ value: 'ja', label: 'JA', icon: 'mdiAlphaJCircle', desc: '日本語' },
		{ value: 'ko', label: 'KO', icon: 'mdiAlphaKCircle', desc: '한국어' },
		{ value: 'pl', label: 'PL', icon: 'mdiAlphaPCircle', desc: 'Polski' },
		{ value: 'nl', label: 'NL', icon: 'mdiAlphaNCircle', desc: 'Nederlands' },
		{ value: 'tr', label: 'TR', icon: 'mdiAlphaTCircle', desc: 'Türkçe' },
		{ value: 'ar', label: 'AR', icon: 'mdiAlphaACircle', desc: 'العربية' },
		{ value: 'hi', label: 'HI', icon: 'mdiAlphaHCircle', desc: 'हिन्दी' },
	]);

	async function updateChatLanguage(botId: number, chat: TelegramChat, lang: string) {
		try {
			await api(`/telegram-bots/${botId}/chats/${chat.id}`, {
				method: 'PUT',
				body: JSON.stringify({ language_override: lang }),
			});
			chats[botId] = (chats[botId] || []).map(c =>
				c.id === chat.id ? { ...c, language_override: lang } : c
			);
			snackSuccess(t('telegramBot.languageUpdated'));
		} catch (err: unknown) { snackError(errMsg(err)); }
	}

	async function toggleChatCommands(botId: number, chat: TelegramChat) {
		const newVal = !chat.commands_enabled;
		try {
			await api(`/telegram-bots/${botId}/chats/${chat.id}`, {
				method: 'PUT',
				body: JSON.stringify({ commands_enabled: newVal }),
			});
			chats[botId] = (chats[botId] || []).map(c =>
				c.id === chat.id ? { ...c, commands_enabled: newVal } : c
			);
		} catch (err: unknown) { snackError(errMsg(err)); }
	}

	async function loadListenerStatus(botId: number) {
		botListenerLoading = { ...botListenerLoading, [botId]: true };
		try {
			const trackers = await api<CommandTrackerSummary[]>('/command-trackers');
			const matched: CommandTrackerSummary[] = [];
			for (const trk of trackers) {
				try {
					const listeners = await api<ListenerEntry[]>(`/command-trackers/${trk.id}/listeners`);
					const hasBot = listeners.some((l) => l.listener_type === 'telegram_bot' && l.listener_id === botId);
					if (hasBot) matched.push(trk);
				} catch (e) { console.warn('Failed to load listeners for tracker:', e); }
			}
			botListenerStatus = { ...botListenerStatus, [botId]: matched };
		} catch (e) { console.warn('Failed to load listener status:', e); botListenerStatus = { ...botListenerStatus, [botId]: [] }; }
		botListenerLoading = { ...botListenerLoading, [botId]: false };
	}

	async function toggleListenerEnabled(botId: number, trk: CommandTrackerSummary) {
		const endpoint = trk.enabled ? 'disable' : 'enable';
		try {
			await api(`/command-trackers/${trk.id}/${endpoint}`, { method: 'POST' });
			botListenerStatus = {
				...botListenerStatus,
				[botId]: (botListenerStatus[botId] || []).map(t =>
					t.id === trk.id ? { ...t, enabled: !t.enabled } : t
				),
			};
		} catch (err: unknown) { snackError(errMsg(err)); }
	}

	async function syncCommands(botId: number) {
		modeChanging = { ...modeChanging, [botId]: true };
		try {
			const res = await api<ApiResult>(`/telegram-bots/${botId}/sync-commands`, { method: 'POST' });
			if (res.success) snackSuccess(t('telegramBot.commandsSynced'));
			else snackError(res.error || t('telegramBot.saveFailed'));
		} catch (err: unknown) { snackError(errMsg(err)); }
		modeChanging = { ...modeChanging, [botId]: false };
	}

	async function switchMode(botId: number, mode: string) {
		modeChanging = { ...modeChanging, [botId]: true };
		try {
			await api(`/telegram-bots/${botId}`, { method: 'PUT', body: JSON.stringify({ update_mode: mode }) });
			await onreload();
			if (mode === 'webhook') {
				await loadWebhookStatus(botId);
			}
			snackSuccess(t('snack.botUpdated'));
		} catch (err: unknown) { snackError(errMsg(err)); }
		modeChanging = { ...modeChanging, [botId]: false };
	}

	async function loadWebhookStatus(botId: number) {
		try {
			webhookStatus = { ...webhookStatus, [botId]: await api<WebhookStatusInfo>(`/telegram-bots/${botId}/webhook/status`) };
		} catch (e) { console.warn('Failed to load webhook status:', e); webhookStatus = { ...webhookStatus, [botId]: {} }; }
	}

	async function registerWebhook(botId: number) {
		modeChanging = { ...modeChanging, [botId]: true };
		try {
			const res = await api<ApiResult>(`/telegram-bots/${botId}/webhook/register`, { method: 'POST' });
			if (res.success) {
				snackSuccess(res.verified ? t('telegramBot.webhookVerified') : t('telegramBot.webhookRegistered'));
				await loadWebhookStatus(botId);
			} else {
				snackError(res.error || t('telegramBot.webhookFailed'));
			}
		} catch (err: unknown) { snackError(errMsg(err)); }
		modeChanging = { ...modeChanging, [botId]: false };
	}

	async function unregisterWebhook(botId: number) {
		modeChanging = { ...modeChanging, [botId]: true };
		try {
			const res = await api<ApiResult>(`/telegram-bots/${botId}/webhook/unregister`, { method: 'POST' });
			if (res.success) { snackSuccess(t('telegramBot.webhookUnregistered')); await loadWebhookStatus(botId); }
			else snackError(res.error || t('telegramBot.saveFailed'));
		} catch (err: unknown) { snackError(errMsg(err)); }
		modeChanging = { ...modeChanging, [botId]: false };
	}

	function copyChatId(e: Event, chatId: string) {
		e.stopPropagation();
		if (navigator.clipboard?.writeText) {
			navigator.clipboard.writeText(chatId);
		} else {
			// Fallback for non-HTTPS contexts
			const ta = document.createElement('textarea');
			ta.value = chatId;
			ta.style.position = 'fixed';
			ta.style.opacity = '0';
			document.body.appendChild(ta);
			ta.select();
			document.execCommand('copy');
			document.body.removeChild(ta);
		}
		snackInfo(`${t('snack.copied')}: ${chatId}`);
	}

	async function testChat(e: Event, botId: number, chatId: string) {
		e.stopPropagation();
		const key = `${botId}_${chatId}`;
		if (chatTesting[key]) return;
		chatTesting = { ...chatTesting, [key]: true };
		try {
			const res = await api<ApiResult>(`/telegram-bots/${botId}/chats/${chatId}/test?locale=${getLocale()}`, { method: 'POST' });
			if (res.success) snackSuccess(t('snack.targetTestSent'));
			else snackError(res.error || t('telegramBot.saveFailed'));
		} catch (err: unknown) { snackError(errMsg(err)); }
		chatTesting = { ...chatTesting, [key]: false };
	}

	function chatTypeLabel(type: string): string {
		const map: Record<string, string> = {
			private: t('telegramBot.private'),
			group: t('telegramBot.group'),
			supergroup: t('telegramBot.supergroup'),
			channel: t('telegramBot.channel'),
		};
		return map[type] || type;
	}
</script>

<PageHeader
	title={t('telegramBot.title')}
	emphasis={t('telegramBot.titleEmphasis')}
	description={t('telegramBot.description')}
	crumb={t('crumbs.operatorsBots')}
	count={bots.length}
	countLabel={t('telegramBot.countLabel')}
>
	<Button size="sm" onclick={() => { showForm ? (showForm = false, editing = null) : openNew(); }}>
		{showForm ? t('common.cancel') : t('telegramBot.addBot')}
	</Button>
</PageHeader>

{#if showForm}
	<Card class="mb-6">
		<ErrorBanner message={error} />
		<form onsubmit={saveBot} class="space-y-3">
			<div>
				<label for="bot-name" class="block text-sm font-medium mb-1">{t('telegramBot.name')}</label>
				<div class="flex gap-2">
					<IconPicker value={form.icon} onselect={(v: string) => form.icon = v} />
					<input id="bot-name" bind:value={form.name} oninput={() => nameManuallyEdited = true} required placeholder={t('telegramBot.namePlaceholder')}
						class="flex-1 px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]" />
				</div>
			</div>
			{#if !editing}
			<div>
				<label for="bot-token" class="block text-sm font-medium mb-1">{t('telegramBot.token')}</label>
				<input id="bot-token" bind:value={form.token} required placeholder={t('telegramBot.tokenPlaceholder')}
					class="w-full px-3 py-2 border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)] font-mono" />
			</div>
			{/if}
			<Button type="submit" disabled={submitting}>
				{submitting ? t('common.loading') : (editing ? t('common.save') : t('telegramBot.addBot'))}
			</Button>
		</form>
	</Card>
{/if}

{#if bots.length === 0 && !showForm}
	<Card>
		<EmptyState icon="mdiRobot" message={t('telegramBot.noBots')} />
	</Card>
{:else}
	<div class="list-stack stagger-children">
		{#each bots as bot}
			<Card hover entityId={bot.id}>
				<div class="list-row">
					<div class="list-row__identity">
						<div class="flex items-center gap-2 flex-wrap min-w-0">
							<span style="color: var(--color-primary);" class="shrink-0"><MdiIcon name={bot.icon || 'mdiRobot'} size={20} /></span>
							<p class="font-medium truncate">{bot.name}</p>
							{#if bot.bot_username}
								<span class="text-xs text-[var(--color-muted-foreground)] shrink-0">@{bot.bot_username}</span>
							{/if}
						</div>
						<div class="list-row__secondary mt-0.5 flex items-center gap-2 flex-wrap">
							<span class="text-xs px-1.5 py-0.5 rounded font-mono {(bot.update_mode || 'none') === 'webhook'
								? 'bg-[var(--color-primary)]/10 text-[var(--color-primary)]'
								: (bot.update_mode || 'none') === 'polling'
									? 'bg-[var(--color-success-bg)] text-[var(--color-success-fg)]'
									: 'bg-[var(--color-muted)] text-[var(--color-muted-foreground)]'}">
								{(bot.update_mode || 'none') === 'webhook' ? t('telegramBot.webhook') : (bot.update_mode || 'none') === 'polling' ? t('telegramBot.polling') : t('telegramBot.none')}
							</span>
							<p class="text-xs text-[var(--color-muted-foreground)] font-mono">{bot.token_preview}</p>
						</div>
					</div>
					<MetaStrip tiles={telegramBotTiles(bot)} />
					<div class="list-row__actions flex-wrap justify-end">
						<IconButton icon="mdiPencil" title={t('common.edit')} onclick={() => editBot(bot)} />
						<button onclick={() => toggleSection(bot.id, 'chats')}
							disabled={chatsLoading[bot.id]}
							class="text-xs text-[var(--color-muted-foreground)] hover:underline px-2 py-1 whitespace-nowrap disabled:opacity-50">
							{t('telegramBot.chats')} {chatsLoading[bot.id] ? '…' : expandedSection[bot.id] === 'chats' ? '▲' : '▼'}
						</button>
						<button onclick={() => toggleSection(bot.id, 'listeners')}
							disabled={botListenerLoading[bot.id]}
							class="text-xs text-[var(--color-muted-foreground)] hover:underline px-2 py-1 whitespace-nowrap disabled:opacity-50">
							{t('commandTracker.listeners')} {botListenerLoading[bot.id] ? '…' : expandedSection[bot.id] === 'listeners' ? '▲' : '▼'}
						</button>
						<IconButton icon="mdiSync" title={t('telegramBot.syncCommands')} onclick={() => syncCommands(bot.id)} disabled={modeChanging[bot.id]} />
						<IconButton icon="mdiDelete" title={t('common.delete')} onclick={() => remove(bot.id)} variant="danger" />
					</div>
				</div>

				<!-- Chats section -->
				{#if expandedSection[bot.id] === 'chats'}
					<div class="mt-3 border-t border-[var(--color-border)] pt-3" in:slide>
						{#if chatsLoading[bot.id] && !chats[bot.id]}
							<p class="text-xs text-[var(--color-muted-foreground)]">{t('common.loading')}</p>
						{:else if (chats[bot.id] || []).length === 0 && !chatsRefreshing[bot.id]}
							<p class="text-xs text-[var(--color-muted-foreground)]">{t('telegramBot.noChats')}</p>
						{:else}
							{@const gridStyle = "display:grid; grid-template-columns:1fr 80px 40px 100px 50px 130px 60px; align-items:center; gap:0.5rem;"}
							<div class="chat-list-wrap" class:is-refreshing={chatsRefreshing[bot.id]}>
								{#if chatsRefreshing[bot.id]}
									<div class="chat-shimmer" aria-hidden="true" transition:fade={{ duration: 180 }}></div>
								{/if}
								<!-- Header -->
								<div style="{gridStyle} padding:0.25rem 0.5rem; border-bottom:1px solid var(--color-border);"
									class="text-[0.65rem] font-semibold uppercase tracking-wide text-[var(--color-muted-foreground)]">
									<span>{t('telegramBot.chatName')}</span>
									<span style="text-align:center">{t('telegramBot.chatType')}</span>
									<span style="text-align:center">{t('telegramBot.chatLang')}</span>
									<span style="text-align:center">{t('telegramBot.langOverride')}</span>
									<span style="text-align:center">{t('telegramBot.cmds')}</span>
									<span style="text-align:center">{t('telegramBot.chatId')}</span>
									<span></span>
								</div>
								<!-- Rows -->
								{#each (chats[bot.id] || []) as chat (chat.id)}
									<div style={gridStyle}
										class="chat-row text-sm px-2 py-1.5 rounded hover:bg-[var(--color-muted)] cursor-pointer"
										animate:flip={{ duration: 280 }}
										in:fade={{ duration: 220, delay: 60 }}
										out:fade={{ duration: 140 }}
										onclick={(e: MouseEvent) => copyChatId(e, chat.chat_id)}
										onkeydown={(e: KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); copyChatId(e as unknown as MouseEvent, chat.chat_id); } }}
										title={t('telegramBot.clickToCopy')}
										aria-label={t('telegramBot.clickToCopy')}
										role="button" tabindex="0">
										<span class="font-medium truncate">{chat.title || chat.username || t('common.unknown')}</span>
										<span style="text-align:center" class="text-xs px-1.5 py-0.5 rounded bg-[var(--color-muted)] text-[var(--color-muted-foreground)]">{chatTypeLabel(chat.type)}</span>
										<span style="text-align:center" class="text-xs text-[var(--color-muted-foreground)]">{(chat.language_code || '—').toUpperCase()}</span>
										<div style="justify-self:center" role="presentation" onclick={(e: MouseEvent) => e.stopPropagation()} onkeydown={(e: KeyboardEvent) => e.stopPropagation()}>
											<EntitySelect
												items={LANG_ITEMS}
												value={chat.language_override || ''}
												size="sm"
												onselect={(val) => updateChatLanguage(bot.id, chat, String(val ?? ''))}
											/>
										</div>
										<div style="justify-self:center" role="presentation" onclick={(e: MouseEvent) => e.stopPropagation()} onkeydown={(e: KeyboardEvent) => e.stopPropagation()}>
											<button
												type="button"
												role="switch"
												aria-checked={chat.commands_enabled}
												aria-label={t('telegramBot.commandsToggle')}
												style="width:28px; height:16px; border-radius:8px; position:relative; transition:background-color 0.2s; background-color:{chat.commands_enabled ? 'var(--color-primary)' : 'var(--color-border)'};"
												title={t('telegramBot.commandsToggle')}
												onclick={() => toggleChatCommands(bot.id, chat)}>
												<span style="position:absolute; top:2px; width:12px; height:12px; border-radius:50%; transition:left 0.2s; left:{chat.commands_enabled ? '14px' : '2px'}; background:{chat.commands_enabled ? 'white' : 'var(--color-muted-foreground)'};" ></span>
											</button>
										</div>
										<span style="text-align:center" class="text-xs text-[var(--color-muted-foreground)] font-mono">{chat.chat_id}</span>
										<div style="justify-self:end" class="flex items-center gap-1">
											<IconButton icon="mdiSend" title={t('common.test')} size={14}
												onclick={(e: MouseEvent) => testChat(e, bot.id, chat.chat_id)}
												disabled={chatTesting[`${bot.id}_${chat.chat_id}`]} />
											<IconButton icon="mdiDelete" title={t('common.delete')} size={14}
												onclick={(e: MouseEvent) => { e.stopPropagation(); deleteChat(bot.id, chat.id); }} variant="danger" />
										</div>
									</div>
								{/each}
								{#if chatsRefreshing[bot.id] && (chats[bot.id] || []).length === 0}
									<p class="text-xs text-[var(--color-muted-foreground)] py-2 px-2">{t('telegramBot.discoveringChats')}</p>
								{/if}
							</div>
						{/if}
						<button onclick={() => discoverChats(bot.id)}
							disabled={chatsRefreshing[bot.id]}
							class="discover-btn text-xs text-[var(--color-primary)] hover:underline mt-2 flex items-center gap-1 disabled:opacity-70 disabled:cursor-default disabled:no-underline">
							<span class="discover-icon" class:is-spinning={chatsRefreshing[bot.id]}>
								<MdiIcon name="mdiSync" size={14} />
							</span>
							{chatsRefreshing[bot.id] ? t('telegramBot.discoveringChats') : t('telegramBot.discoverChats')}
						</button>
					</div>
				{/if}
				<!-- Listener Status section -->
				{#if expandedSection[bot.id] === 'listeners'}
					<div class="mt-3 border-t border-[var(--color-border)] pt-3 space-y-3" in:slide>
						{#if botListenerLoading[bot.id]}
							<p class="text-xs text-[var(--color-muted-foreground)]">{t('common.loading')}</p>
						{:else if (botListenerStatus[bot.id] || []).length === 0}
							<p class="text-xs text-[var(--color-muted-foreground)]">{t('commandTracker.noListeners')}</p>
						{:else}
							<div class="space-y-1">
								{#each botListenerStatus[bot.id] as trk}
									<div class="flex items-center justify-between text-sm px-2 py-1 rounded hover:bg-[var(--color-muted)]">
										<div class="flex items-center gap-2">
											<MdiIcon name={trk.icon || 'mdiConsoleLine'} size={14} />
											<a href="/command-trackers" class="font-medium text-[var(--color-primary)] hover:underline">{trk.name}</a>
										</div>
										<button
											type="button"
											role="switch"
											aria-checked={trk.enabled}
											aria-label={trk.enabled ? t('notificationTracker.pause') : t('notificationTracker.resume')}
											style="width:28px; height:16px; border-radius:8px; position:relative; transition:background-color 0.2s; background-color:{trk.enabled ? 'var(--color-primary)' : 'var(--color-border)'};"
											title={trk.enabled ? t('notificationTracker.pause') : t('notificationTracker.resume')}
											onclick={() => toggleListenerEnabled(bot.id, trk)}>
											<span style="position:absolute; top:2px; width:12px; height:12px; border-radius:50%; transition:left 0.2s; left:{trk.enabled ? '14px' : '2px'}; background:{trk.enabled ? 'white' : 'var(--color-muted-foreground)'};" ></span>
										</button>
									</div>
								{/each}
							</div>
						{/if}

						<!-- Update mode -->
						<div class="border-t border-[var(--color-border)] pt-3">
							<p class="text-xs font-medium mb-2">{t('telegramBot.updateMode')}</p>
							<div class="flex items-center gap-3 flex-wrap">
								<div class="flex items-center rounded-md border border-[var(--color-border)] overflow-hidden">
									<button onclick={() => switchMode(bot.id, 'none')}
										disabled={modeChanging[bot.id] || (bot.update_mode || 'none') === 'none'}
										class="px-3 py-1 text-xs transition-colors {(bot.update_mode || 'none') === 'none'
											? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]'
											: 'hover:bg-[var(--color-muted)]'} disabled:opacity-70">
										<MdiIcon name="mdiBellOff" size={14} />
										{t('telegramBot.none')}
									</button>
									<button onclick={() => switchMode(bot.id, 'polling')}
										disabled={modeChanging[bot.id] || bot.update_mode === 'polling'}
										class="px-3 py-1 text-xs transition-colors {bot.update_mode === 'polling'
											? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]'
											: 'hover:bg-[var(--color-muted)]'} disabled:opacity-70">
										<MdiIcon name="mdiSync" size={14} />
										{t('telegramBot.polling')}
									</button>
									<button onclick={() => switchMode(bot.id, 'webhook')}
										disabled={modeChanging[bot.id] || bot.update_mode === 'webhook'}
										class="px-3 py-1 text-xs transition-colors {bot.update_mode === 'webhook'
											? 'bg-[var(--color-primary)] text-[var(--color-primary-foreground)]'
											: 'hover:bg-[var(--color-muted)]'} disabled:opacity-70">
										<MdiIcon name="mdiWebhook" size={14} />
										{t('telegramBot.webhook')}
									</button>
								</div>

								{#if (bot.update_mode || 'none') === 'none'}
									<span class="text-xs text-[var(--color-muted-foreground)] flex items-center gap-1">
										<MdiIcon name="mdiBellOff" size={14} />
										{t('telegramBot.noneActive')}
									</span>
								{/if}

								{#if bot.update_mode === 'polling'}
									<span class="text-xs text-[var(--color-success-fg)] flex items-center gap-1">
										<MdiIcon name="mdiCheckCircle" size={14} />
										{t('telegramBot.pollingActive')}
									</span>
								{/if}

								{#if bot.update_mode === 'webhook'}
									<button onclick={() => registerWebhook(bot.id)} disabled={modeChanging[bot.id]}
										class="px-2 py-1 text-xs border border-[var(--color-border)] rounded-md hover:bg-[var(--color-muted)] disabled:opacity-50">
										{t('telegramBot.registerWebhook')}
									</button>
									<button onclick={() => unregisterWebhook(bot.id)} disabled={modeChanging[bot.id]}
										class="px-2 py-1 text-xs text-[var(--color-muted-foreground)] hover:underline disabled:opacity-50">
										{t('telegramBot.unregisterWebhook')}
									</button>
									{#if webhookStatus[bot.id]}
										{@const ws = webhookStatus[bot.id]}
										<span class="text-xs font-mono {ws.url ? 'text-blue-500' : 'text-[var(--color-muted-foreground)]'}">
											{ws.url ? t('telegramBot.webhookActive') : t('telegramBot.webhookNotSet')}
											{#if (ws.pending_update_count ?? 0) > 0}
												({ws.pending_update_count} {t('telegramBot.pendingUpdates')})
											{/if}
										</span>
										{#if ws.last_error_message}
											<span class="text-xs text-[var(--color-error-fg)]">{t('telegramBot.webhookError')}: {ws.last_error_message}</span>
										{/if}
									{:else}
										<button onclick={() => loadWebhookStatus(bot.id)}
											class="text-xs text-[var(--color-primary)] hover:underline">
											{t('telegramBot.webhookStatus')}
										</button>
									{/if}
								{/if}

								{#if !settings.external_url && bot.update_mode === 'webhook'}
									<span class="text-xs text-[var(--color-warning-fg)] flex items-center gap-1">
										<MdiIcon name="mdiAlert" size={14} />
										{t('telegramBot.noExternalDomain')}
									</span>
								{/if}
							</div>
						</div>
					</div>
				{/if}
			</Card>
		{/each}
	</div>
{/if}

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

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

<style>
	/* Chat list — smooth refresh state.
	   The list stays mounted during Discover; we only dim it slightly
	   and run a thin shimmer bar across the top so the user sees
	   "refreshing" instead of "everything vanished and came back". */
	.chat-list-wrap {
		position: relative;
		transition: opacity 0.25s ease, filter 0.25s ease;
	}
	.chat-list-wrap.is-refreshing {
		opacity: 0.78;
		filter: saturate(0.9);
	}
	.chat-list-wrap.is-refreshing .chat-row {
		pointer-events: none;
	}
	.chat-shimmer {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		height: 2px;
		overflow: hidden;
		border-radius: 2px;
		background: color-mix(in srgb, var(--color-primary) 12%, transparent);
		z-index: 2;
	}
	.chat-shimmer::after {
		content: '';
		position: absolute;
		inset: 0;
		background: linear-gradient(
			90deg,
			transparent 0%,
			color-mix(in srgb, var(--color-primary) 70%, transparent) 50%,
			transparent 100%
		);
		transform: translateX(-100%);
		animation: chat-shimmer-sweep 1.15s ease-in-out infinite;
	}
	@keyframes chat-shimmer-sweep {
		0%   { transform: translateX(-100%); }
		100% { transform: translateX(100%); }
	}

	.discover-icon {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		transition: transform 0.2s ease;
	}
	.discover-icon.is-spinning {
		animation: discover-spin 1s linear infinite;
	}
	@keyframes discover-spin {
		to { transform: rotate(-360deg); }
	}

	@media (prefers-reduced-motion: reduce) {
		.chat-shimmer::after,
		.discover-icon.is-spinning {
			animation: none;
		}
		.chat-list-wrap {
			transition: none;
		}
	}
</style>

