<script lang="ts">
	import { apiFetch, apiMutate } from '$lib/api/client.js';
	import type { BgpCommunity, DomainEntry, ModuleRow } from '$lib/api/types.js';
	import {
		communityLabel,
		sanitizeFilenamePart,
		supportsCsvIO
	} from '$lib/components/modules/module-helpers.js';
	import { Button } from '@evobgp/ui/components/button/index.js';
	import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
	import {
		Card,
		CardContent,
		CardHeader,
		CardTitle,
		CardDescription
	} from '@evobgp/ui/components/card/index.js';
	import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
	import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
	import { notify, notifyApiError } from '$lib/ui/app/toast.js';
	import ModuleDomainEntryDialog from '$lib/components/modules/ModuleDomainEntryDialog.svelte';
	import Plus from '@lucide/svelte/icons/plus';
	import Pencil from '@lucide/svelte/icons/pencil';
	import Trash2 from '@lucide/svelte/icons/trash-2';
	import Upload from '@lucide/svelte/icons/upload';
	import Download from '@lucide/svelte/icons/download';

	type Props = {
		moduleId: string;
		mod: ModuleRow;
		entries: DomainEntry[];
		communities: BgpCommunity[];
		loading?: boolean;
		onChanged: () => void | Promise<void>;
	};

	let { moduleId, mod, entries, communities, loading = false, onChanged }: Props = $props();

	let dialogOpen = $state(false);
	let editTarget = $state<DomainEntry | null>(null);
	let selectedIds = $state(new Set<string>());
	let deletingBulk = $state(false);
	let csvImporting = $state(false);
	let csvExporting = $state(false);
	let csvFileInput = $state<HTMLInputElement | null>(null);

	const activeSelected = $derived.by(() => {
		const allowed = new Set(entries.map((e) => e.id));
		return [...selectedIds].filter((id) => allowed.has(id));
	});
	const selectedCount = $derived(activeSelected.length);
	const allSelected = $derived(entries.length > 0 && entries.every((e) => selectedIds.has(e.id)));

	const columns = [
		{ id: 'select', label: '', class: 'w-10' },
		{ id: 'fqdn', label: 'FQDN', sortable: true, sortValue: (e: DomainEntry) => e.fqdn },
		{ id: 'community', label: 'Community' },
		{ id: 'actions', label: '', class: 'w-20' }
	] as const;

	function toggleSelection(id: string) {
		const next = new Set(selectedIds);
		if (next.has(id)) next.delete(id);
		else next.add(id);
		selectedIds = next;
	}

	function toggleAll(checked: boolean) {
		selectedIds = checked ? new Set(entries.map((e) => e.id)) : new Set<string>();
	}

	function openCreate() {
		editTarget = null;
		dialogOpen = true;
	}

	function openEdit(entry: DomainEntry) {
		editTarget = entry;
		dialogOpen = true;
	}

	function requestDelete(entry: DomainEntry) {
		void confirm({
			title: 'Удалить домен?',
			description: entry.fqdn,
			confirmLabel: 'Удалить',
			destructive: true,
			onConfirm: async () => {
				await apiMutate(`/v1/modules/${moduleId}/domain-entries/${entry.id}`, 'DELETE', undefined, {
					idempotent: false
				});
				notify.success('Удалено');
				await onChanged();
			}
		});
	}

	function requestBulkDelete() {
		if (selectedCount === 0) return;
		void confirm({
			title: 'Удалить выбранные домены?',
			description: `Будет удалено: ${selectedCount}`,
			confirmLabel: 'Удалить',
			destructive: true,
			onConfirm: bulkDelete
		});
	}

	async function bulkDelete() {
		if (selectedCount === 0) return;
		deletingBulk = true;
		let deleted = 0;
		try {
			for (const id of activeSelected) {
				try {
					await apiMutate(`/v1/modules/${moduleId}/domain-entries/${id}`, 'DELETE', undefined, {
						idempotent: false
					});
					deleted += 1;
				} catch (e) {
					notifyApiError(e);
				}
			}
			if (deleted > 0) notify.success(`Удалено доменов: ${deleted}`);
			await onChanged();
		} finally {
			deletingBulk = false;
		}
	}

	async function readErrorText(res: Response): Promise<string> {
		const body = (await res.text()).trim();
		return body || `HTTP ${res.status}`;
	}

	async function exportCsv() {
		if (!supportsCsvIO(mod.type) || csvExporting) return;
		csvExporting = true;
		try {
			const res = await apiFetch(`/v1/modules/${moduleId}/entries.csv`, {
				method: 'GET',
				headers: { Accept: 'text/csv' }
			});
			if (!res.ok) {
				notify.error(await readErrorText(res));
				return;
			}
			const blob = await res.blob();
			const url = URL.createObjectURL(blob);
			const a = document.createElement('a');
			a.href = url;
			a.download = `${sanitizeFilenamePart(mod.name)}-${mod.type.toLowerCase()}-entries.csv`;
			document.body.appendChild(a);
			a.click();
			a.remove();
			URL.revokeObjectURL(url);
		} catch (e) {
			notifyApiError(e);
		} finally {
			csvExporting = false;
		}
	}

	function openImportPicker() {
		if (!supportsCsvIO(mod.type) || csvImporting) return;
		csvFileInput?.click();
	}

	async function handleImportChange(event: Event) {
		const input = event.currentTarget as HTMLInputElement | null;
		const file = input?.files?.[0];
		if (!file || csvImporting) return;
		csvImporting = true;
		try {
			const fileText = await file.text();
			const res = await apiFetch(`/v1/modules/${moduleId}/entries.csv`, {
				method: 'POST',
				headers: { 'Content-Type': 'text/csv' },
				body: fileText
			});
			if (!res.ok) {
				notify.error(await readErrorText(res));
				return;
			}
			const payload = (await res.json()) as { imported?: number };
			notify.success(`Импортировано записей: ${payload.imported ?? 0}`);
			await onChanged();
		} catch (e) {
			notifyApiError(e);
		} finally {
			csvImporting = false;
			if (input) input.value = '';
		}
	}
</script>

<input
	class="hidden"
	type="file"
	accept=".csv,text/csv"
	bind:this={csvFileInput}
	onchange={handleImportChange}
/>

<Card>
	<CardHeader class="flex flex-col gap-3 pb-2 sm:flex-row sm:items-center sm:justify-between">
		<div class="min-w-0 flex-1">
			<CardTitle class="text-base">Домены</CardTitle>
			<CardDescription>FQDN для резолвинга через DoH.</CardDescription>
		</div>
		<div class="flex shrink-0 flex-wrap items-center justify-end gap-2 self-start sm:self-auto">
			<Button
				variant="outline"
				size="sm"
				onclick={openImportPicker}
				disabled={!supportsCsvIO(mod.type) || csvImporting || csvExporting}
			>
				<Upload />
				{csvImporting ? 'Импорт…' : 'Импорт CSV'}
			</Button>
			<Button
				variant="outline"
				size="sm"
				onclick={exportCsv}
				disabled={!supportsCsvIO(mod.type) || csvExporting || csvImporting}
			>
				<Download />
				{csvExporting ? 'Экспорт…' : 'Экспорт CSV'}
			</Button>
			<Button size="sm" onclick={openCreate}><Plus />Добавить</Button>
			{#if selectedCount > 0}
				<Button variant="destructive" size="sm" onclick={requestBulkDelete} disabled={deletingBulk}>
					<Trash2 />
					Удалить ({selectedCount})
				</Button>
			{/if}
		</div>
	</CardHeader>
	<CardContent class="p-4 pt-0">
		<AppDataTable
			columns={[...columns]}
			rows={entries}
			rowKey={(e) => e.id}
			{loading}
			emptyTitle="Нет доменов"
			emptyDescription="Добавьте FQDN или импортируйте CSV."
		>
			{#snippet toolbar()}
				{#if entries.length > 0}
					<div class="flex items-center gap-2">
						<Checkbox
							checked={allSelected}
							onCheckedChange={(v) => toggleAll(v === true)}
							aria-label="Выбрать все домены"
						/>
						<span class="text-sm text-muted-foreground">Выбрать все</span>
					</div>
				{/if}
			{/snippet}
			{#snippet cell({ row: entry, column })}
				{#if column.id === 'select'}
					<Checkbox
						checked={selectedIds.has(entry.id)}
						aria-label={`Выбрать домен ${entry.fqdn}`}
						onCheckedChange={() => toggleSelection(entry.id)}
					/>
				{:else if column.id === 'fqdn'}
					<span class="font-mono">{entry.fqdn}</span>
				{:else if column.id === 'community'}
					<span class="text-sm text-muted-foreground">
						{communityLabel(entry.community_id, communities)}
					</span>
				{:else if column.id === 'actions'}
					<div class="flex gap-1">
						<Button variant="ghost" size="icon-sm" onclick={() => openEdit(entry)}>
							<Pencil class="size-3.5" />
						</Button>
						<Button
							variant="ghost"
							size="icon-sm"
							class="text-destructive"
							onclick={() => requestDelete(entry)}
						>
							<Trash2 class="size-3.5" />
						</Button>
					</div>
				{/if}
			{/snippet}
		</AppDataTable>
	</CardContent>
</Card>

<ModuleDomainEntryDialog
	bind:open={dialogOpen}
	{moduleId}
	edit={editTarget}
	{communities}
	onSaved={onChanged}
	onClose={() => {
		editTarget = null;
	}}
/>
