<script lang="ts">
	import { ArrowLeft, Receipt, Plus, Trash2, Send, FileCheck, X, FileText, Check, ClipboardCheck, Stamp, FileCode, AlertTriangle } from '@lucide/svelte';
	import { page } from '$app/state';
	import * as Card from '$lib/components/ui/card';
	import * as Table from '$lib/components/ui/table';
	import { Button } from '$lib/components/ui/button';
	import { companyStore } from '$lib/stores/company.svelte';
	import {
		invoicesAPI, invoiceItemsAPI, paymentsAPI, conceptsAPI, stampingAPI, satCatalogsAPI, MissingFiscalDataError,
		type Concept, type Invoice, type InvoiceInput, type InvoiceItem, type InvoiceItemInput, type Payment, type PaymentInput, type InvoiceStamp,
		type SatCatalogItem, type SatUnitOfMeasure
	} from '$lib/api/fin';
	import { accountsAPI, type Account } from '$lib/api/crm';
	import { INVOICE_STATUS, QUOTE_CONCEPTS, PAYMENT_METHODS, labelOf, formatMoney } from '$lib/components/crm/format';
	import { toast } from 'svelte-sonner';

	const invoiceId = $derived(Number(page.params.id));
	const companyId = $derived(companyStore.activeCompany?.id ?? null);

	let invoice = $state<Invoice | null>(null);
	let items = $state<InvoiceItem[]>([]);
	let payments = $state<Payment[]>([]);
	let accounts = $state<Account[]>([]);
	/** Catálogo de conceptos de la empresa; se cargan todos para poder etiquetar
	 *  partidas que apunten a un concepto ya inactivo. */
	let concepts = $state<Concept[]>([]);
	let form = $state<InvoiceInput>({});
	let tab = $state('conceptos');
	let loading = $state(false);
	let saving = $state(false);
	let busy = $state(false);
	let addingItem = $state(false);
	let addingPay = $state(false);
	let newItem = $state<InvoiceItemInput>({ invoice_id: 0, concept: 'flete_internacional', quantity: 1, unit_amount: 0 });
	let newPay = $state<PaymentInput>({ invoice_id: 0, amount: 0, method: 'transferencia' });
	/** Opción elegida en el selector de concepto: `cat:<id>` del catálogo o `txt:<clave>` genérica. */
	let conceptChoice = $state('txt:flete_internacional');
	/** Timbre vigente de la factura; `null` mientras no esté timbrada. */
	let stamp = $state<InvoiceStamp | null>(null);
	let stamping = $state(false);
	/** Datos fiscales que el backend reportó como faltantes en el último intento. */
	let missing = $state<string[]>([]);
	// Catálogos SAT para los selectores fiscales. Se cargan una vez con la factura.
	let paymentForms = $state<SatCatalogItem[]>([]);
	let paymentMethods = $state<SatCatalogItem[]>([]);
	let taxObjects = $state<SatCatalogItem[]>([]);
	let unitsOfMeasure = $state<SatUnitOfMeasure[]>([]);
	let productsServices = $state<SatCatalogItem[]>([]);
	/** Partida cuyas claves fiscales se están editando. */
	let fiscalItem = $state<InvoiceItem | null>(null);
	let fiscalForm = $state<{ product_service_id: number | null; unit_of_measure_id: number | null; tax_object_id: number | null }>({
		product_service_id: null, unit_of_measure_id: null, tax_object_id: null
	});

	const activeConcepts = $derived(concepts.filter((c) => c.is_active));

	$effect(() => {
		const cid = companyId;
		const id = invoiceId;
		if (!cid || !id) return;
		void load(cid, id);
		void loadStamp(cid, id);
	});

	async function load(cid: number, id: number) {
		loading = true;
		try {
			[invoice, items, payments, accounts, concepts,
			 paymentForms, paymentMethods, taxObjects, unitsOfMeasure, productsServices] = await Promise.all([
				invoicesAPI.get(id, cid), invoicesAPI.items(id, cid), invoicesAPI.payments(id, cid),
				accountsAPI.list(cid), conceptsAPI.list(cid),
				satCatalogsAPI.paymentForms(cid), satCatalogsAPI.paymentMethods(cid),
				satCatalogsAPI.taxObjects(cid), satCatalogsAPI.unitsOfMeasure(cid),
				satCatalogsAPI.productsServices(cid)
			]);
			form = { ...invoice };
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo cargar la factura');
		} finally {
			loading = false;
		}
	}

	async function reload() {
		if (companyId) {
			[invoice, items, payments] = await Promise.all([
				invoicesAPI.get(invoiceId, companyId), invoicesAPI.items(invoiceId, companyId), invoicesAPI.payments(invoiceId, companyId)
			]);
			form = { ...invoice };
		}
	}

	async function saveHeader() {
		if (!companyId || !invoice) return;
		saving = true;
		try {
			invoice = await invoicesAPI.update(invoice.id, form, companyId);
			await reload();
			toast.success('Cambios guardados');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo guardar');
		} finally {
			saving = false;
		}
	}

	async function doAction(action: 'emit' | 'send' | 'cancel') {
		if (!companyId || !invoice) return;
		busy = true;
		try {
			invoice = await invoicesAPI[action](invoice.id, companyId);
			form = { ...invoice };
			toast.success(action === 'send' ? 'Factura enviada (PDF generado)' : 'Factura actualizada');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo actualizar');
		} finally {
			busy = false;
		}
	}

	async function openPdf() {
		if (!companyId || !invoice) return;
		try {
			const { url } = await invoicesAPI.pdfUrl(invoice.id, companyId);
			window.open(url, '_blank', 'noopener');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo abrir el PDF');
		}
	}

	async function markReview() {
		if (!companyId || !invoice) return;
		busy = true;
		try {
			invoice = await invoicesAPI.markClientReview(invoice.id, companyId);
			form = { ...invoice };
			toast.success('Factura en revisión del cliente');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo actualizar');
		} finally {
			busy = false;
		}
	}

	async function clientDecision(approved: boolean) {
		if (!companyId || !invoice) return;
		const notes = approved ? null : (window.prompt('Observaciones del cliente:') ?? null);
		busy = true;
		try {
			invoice = await invoicesAPI.clientDecision(invoice.id, approved, companyId, notes);
			form = { ...invoice };
			toast.success(approved ? 'Aprobada por el cliente' : 'Registrada con observaciones');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo actualizar');
		} finally {
			busy = false;
		}
	}

	async function loadStamp(cid: number, id: number) {
		try {
			stamp = await stampingAPI.get(id, cid);
		} catch {
			// Consultar el timbre es informativo: si falla, la pantalla sigue siendo usable.
			stamp = null;
		}
	}

	async function doStamp() {
		if (!companyId || !invoice) return;
		if (invoice.stamping_mode === 'produccion') {
			// Producción emite un CFDI con validez fiscal real ante el SAT y deshacerlo obliga a
			// cancelarlo: no puede dispararse con un clic distraído.
			const ok = window.confirm(
				'Esta factura está en modo PRODUCCIÓN.\n\n' +
					'Se emitirá un CFDI con validez fiscal real ante el SAT y para deshacerlo habrá que ' +
					'cancelarlo.\n\n¿Continuar?'
			);
			if (!ok) return;
		}
		missing = [];
		stamping = true;
		try {
			stamp = await stampingAPI.stamp(invoice.id, companyId);
			toast.success(`Comprobante timbrado — UUID ${stamp.uuid}`);
			await reload();
		} catch (e) {
			if (e instanceof MissingFiscalDataError) {
				// Se pintan en la pantalla, no en un toast: son varios y hay que ir a capturarlos.
				missing = e.missing;
				toast.error(e.message);
			} else {
				toast.error(e instanceof Error ? e.message : 'No se pudo timbrar');
			}
		} finally {
			stamping = false;
		}
	}

	async function openStampXml() {
		if (!companyId || !invoice) return;
		try {
			const url = await stampingAPI.xmlUrl(invoice.id, companyId);
			window.open(url, '_blank', 'noopener');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudo abrir el XML');
		}
	}

	function startFiscal(it: InvoiceItem) {
		fiscalItem = it;
		fiscalForm = {
			product_service_id: it.product_service_id ?? null,
			unit_of_measure_id: it.unit_of_measure_id ?? null,
			tax_object_id: it.tax_object_id ?? null
		};
	}

	async function saveFiscal() {
		if (!companyId || !fiscalItem) return;
		busy = true;
		try {
			// El backend deriva el IVA de la partida al guardar: si el objeto de impuesto pasa a
			// '02', el traslado aparece solo con el % de la factura.
			await invoiceItemsAPI.update(fiscalItem.id, fiscalForm, companyId);
			fiscalItem = null;
			await reload();
			toast.success('Claves fiscales actualizadas');
		} catch (e) {
			toast.error(e instanceof Error ? e.message : 'No se pudieron guardar las claves');
		} finally {
			busy = false;
		}
	}

	function startItem() {
		newItem = { invoice_id: invoiceId, concept: 'flete_internacional', quantity: 1, unit_amount: 0 };
		// Si la empresa ya tiene catálogo, se arranca con su primer concepto.
		conceptChoice = activeConcepts.length ? `cat:${activeConcepts[0].id}` : 'txt:flete_internacional';
		applyConceptChoice();
		addingItem = true;
	}

	/** Traduce la opción del selector a la partida: referencia al catálogo o texto genérico. */
	function applyConceptChoice() {
		if (conceptChoice.startsWith('cat:')) {
			const c = activeConcepts.find((x) => x.id === Number(conceptChoice.slice(4)));
			if (!c) return;
			// Solo se manda concept_id: el backend copia ahí la descripción del concepto.
			newItem.concept_id = c.id;
			newItem.concept = undefined;
			if (c.unit_price !== null && c.unit_price !== undefined) newItem.unit_amount = Number(c.unit_price);
		} else {
			newItem.concept_id = null;
			newItem.concept = conceptChoice.slice(4);
		}
	}

	/** Etiqueta de la partida: el concepto del catálogo si lo tiene, si no el texto libre. */
	function itemConceptLabel(it: InvoiceItem): string {
		const c = it.concept_id ? concepts.find((x) => x.id === it.concept_id) : undefined;
		return c ? `${c.code} — ${c.description}` : labelOf(QUOTE_CONCEPTS, it.concept);
	}

	async function saveItem() {
		if (!companyId) return;
		try { await invoiceItemsAPI.create({ ...newItem, invoice_id: invoiceId }, companyId); addingItem = false; await reload(); toast.success('Concepto agregado'); }
		catch (e) { toast.error(e instanceof Error ? e.message : 'No se pudo agregar'); }
	}
	async function removeItem(it: InvoiceItem) {
		if (!companyId || !confirm('¿Eliminar concepto?')) return;
		await invoiceItemsAPI.remove(it.id, companyId); await reload();
	}

	function startPay() { newPay = { invoice_id: invoiceId, amount: 0, method: 'transferencia' }; addingPay = true; }
	async function savePay() {
		if (!companyId) return;
		try { await paymentsAPI.create({ ...newPay, invoice_id: invoiceId }, companyId); addingPay = false; await reload(); toast.success('Pago registrado'); }
		catch (e) { toast.error(e instanceof Error ? e.message : 'No se pudo registrar'); }
	}
	async function removePay(p: Payment) {
		if (!companyId || !confirm('¿Eliminar pago?')) return;
		await paymentsAPI.remove(p.id, companyId); await reload();
	}

	const statusClass: Record<string, string> = {
		borrador: 'bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400',
		emitida: 'bg-blue-100 text-blue-700 dark:bg-blue-950/40 dark:text-blue-400',
		enviada: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-950/40 dark:text-cyan-400',
		en_revision_cliente: 'bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400',
		pagada: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-400',
		cancelada: 'bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-400'
	};
	const inputCls = 'rounded-md border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring';
</script>

<div class="space-y-6">
	<Button variant="ghost" size="sm" href="/dashboard/fin/facturas"><ArrowLeft class="mr-1 h-4 w-4" /> Facturas</Button>

	{#if loading && !invoice}
		<p class="text-sm text-muted-foreground">Cargando…</p>
	{:else if invoice}
		<div class="flex flex-wrap items-start justify-between gap-3">
			<div>
				<h1 class="flex items-center gap-2 text-2xl font-bold tracking-tight"><Receipt class="h-6 w-6" /> {invoice.reference ?? `Factura #${invoice.id}`}</h1>
				<p class="mt-1 text-sm"><span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium {statusClass[invoice.status] ?? ''}">{labelOf(INVOICE_STATUS, invoice.status)}</span></p>
			</div>
			<div class="flex flex-wrap gap-2">
				{#if invoice.status === 'borrador'}<Button size="sm" variant="outline" onclick={() => doAction('emit')} disabled={busy}><FileCheck class="mr-1 h-4 w-4" /> Emitir</Button>{/if}
				{#if invoice.status === 'emitida'}<Button size="sm" variant="outline" onclick={() => doAction('send')} disabled={busy}><Send class="mr-1 h-4 w-4" /> Enviar (PDF)</Button>{/if}
				{#if invoice.pdf_file_key}<Button size="sm" variant="outline" onclick={openPdf}><FileText class="mr-1 h-4 w-4" /> Ver PDF</Button>{/if}
				{#if invoice.status === 'enviada'}<Button size="sm" variant="outline" onclick={markReview} disabled={busy}><ClipboardCheck class="mr-1 h-4 w-4" /> En revisión</Button>{/if}
				{#if invoice.status === 'enviada' || invoice.status === 'en_revision_cliente'}
					<Button size="sm" variant="outline" onclick={() => clientDecision(true)} disabled={busy}><Check class="mr-1 h-4 w-4 text-emerald-600" /> Cliente aprueba</Button>
					<Button size="sm" variant="outline" onclick={() => clientDecision(false)} disabled={busy}><X class="mr-1 h-4 w-4 text-destructive" /> Con observaciones</Button>
				{/if}
				{#if invoice.status !== 'borrador' && invoice.status !== 'cancelada' && !stamp}
					<Button size="sm" onclick={doStamp} disabled={stamping || busy}>
						<Stamp class="mr-1 h-4 w-4" /> {stamping ? 'Timbrando…' : 'Timbrar'}
					</Button>
				{/if}
				{#if stamp?.xml_file_key}<Button size="sm" variant="outline" onclick={openStampXml}><FileCode class="mr-1 h-4 w-4" /> XML timbrado</Button>{/if}
				{#if invoice.status !== 'cancelada' && invoice.status !== 'pagada'}<Button size="sm" variant="outline" onclick={() => doAction('cancel')} disabled={busy}><X class="mr-1 h-4 w-4" /> Cancelar</Button>{/if}
			</div>
		</div>

		{#if stamp}
			<!-- Comprobante ya timbrado: el UUID es el identificador ante el SAT. -->
			<div class="rounded-md border border-emerald-200 bg-emerald-50 p-3 text-sm dark:border-emerald-900 dark:bg-emerald-950">
				<p class="flex flex-wrap items-center gap-2 font-medium text-emerald-900 dark:text-emerald-100">
					<Stamp class="h-4 w-4" /> Comprobante timbrado
					{#if stamp.mode === 'pruebas'}
						<span class="rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900 dark:text-amber-100">
							PRUEBAS — sin validez fiscal
						</span>
					{/if}
				</p>
				<dl class="mt-2 grid gap-x-6 gap-y-1 text-xs text-emerald-900 sm:grid-cols-2 dark:text-emerald-200">
					<div><dt class="inline font-medium">UUID:</dt> <dd class="inline font-mono">{stamp.uuid}</dd></div>
					<div><dt class="inline font-medium">Fecha de timbrado:</dt> <dd class="inline">{stamp.stamped_at ?? '—'}</dd></div>
					<div><dt class="inline font-medium">PAC:</dt> <dd class="inline">{stamp.pac_rfc ?? '—'}</dd></div>
					{#if stamp.pac_balance !== null}
						<div><dt class="inline font-medium">Folios restantes:</dt> <dd class="inline">{stamp.pac_balance}</dd></div>
					{/if}
				</dl>
				{#if stamp.error_message}
					<p class="mt-2 text-xs text-amber-800 dark:text-amber-200">Aviso: {stamp.error_message}</p>
				{/if}
			</div>
		{/if}

		{#if missing.length}
			<!-- El backend devuelve TODOS los faltantes de una vez, para capturarlos en una pasada. -->
			<div class="rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm">
				<p class="flex items-center gap-2 font-medium text-destructive">
					<AlertTriangle class="h-4 w-4" /> Faltan datos fiscales para timbrar
				</p>
				<ul class="mt-2 list-disc space-y-0.5 pl-6 text-xs text-muted-foreground">
					{#each missing as m (m)}<li>{m}</li>{/each}
				</ul>
				<p class="mt-2 text-xs text-muted-foreground">
					Captúralos en la factura, en sus partidas o en la ficha del cliente y vuelve a intentar.
				</p>
			</div>
		{/if}

		{#if invoice.client_reviewed_at}
			<p class="text-xs text-muted-foreground">Revisión del cliente: {invoice.client_approved ? 'aprobada' : 'con observaciones'}{#if invoice.review_notes} — {invoice.review_notes}{/if}</p>
		{/if}

		<div class="grid gap-4 sm:grid-cols-4">
			<Card.Root><Card.Header><Card.Description>Subtotal</Card.Description><Card.Title class="text-lg">{formatMoney(invoice.subtotal, invoice.currency)}</Card.Title></Card.Header></Card.Root>
			<Card.Root><Card.Header><Card.Description>Impuesto ({invoice.tax_rate}%)</Card.Description><Card.Title class="text-lg">{formatMoney(invoice.tax_amount, invoice.currency)}</Card.Title></Card.Header></Card.Root>
			<Card.Root><Card.Header><Card.Description>Total</Card.Description><Card.Title class="text-lg">{formatMoney(invoice.total, invoice.currency)}</Card.Title></Card.Header></Card.Root>
			<Card.Root><Card.Header><Card.Description>Saldo</Card.Description><Card.Title class="text-lg {invoice.balance > 0 ? 'text-amber-600' : 'text-emerald-600'}">{formatMoney(invoice.balance, invoice.currency)}</Card.Title></Card.Header></Card.Root>
		</div>

		<Card.Root>
			<Card.Content class="pt-6">
				<div class="mb-5 flex flex-wrap gap-1 border-b">
					{#each [{ id: 'conceptos', label: 'Conceptos' }, { id: 'pagos', label: 'Pagos / cobranza' }, { id: 'datos', label: 'Datos' }] as t (t.id)}
						<button type="button" class="border-b-2 px-3 py-2 text-sm font-medium transition-colors {tab === t.id ? 'border-primary text-foreground' : 'border-transparent text-muted-foreground hover:text-foreground'}" onclick={() => (tab = t.id)}>{t.label}</button>
					{/each}
				</div>

				{#if tab === 'conceptos'}
					<div class="mb-3 flex justify-end"><Button size="sm" variant="outline" onclick={startItem}><Plus class="mr-1 h-4 w-4" /> Agregar concepto</Button></div>
					{#if addingItem}
						<div class="mb-4 grid gap-3 rounded-md border p-3 sm:grid-cols-2">
							<label class="flex flex-col gap-1 text-sm">
								<span class="font-medium">Concepto</span>
								<select class={inputCls} bind:value={conceptChoice} onchange={applyConceptChoice}>
									{#if activeConcepts.length > 0}
										<optgroup label="Catálogo de conceptos">
											{#each activeConcepts as c (c.id)}<option value={`cat:${c.id}`}>{c.code} — {c.description}</option>{/each}
										</optgroup>
									{/if}
									<optgroup label="Conceptos genéricos (sin clave del SAT)">
										{#each QUOTE_CONCEPTS as c (c.value)}<option value={`txt:${c.value}`}>{c.label}</option>{/each}
									</optgroup>
								</select>
								{#if activeConcepts.length === 0}
									<span class="text-xs text-muted-foreground">
										El catálogo de conceptos está vacío.
										<a class="underline" href="/dashboard/fin/conceptos">Darlos de alta</a> permite facturar con clave del SAT.
									</span>
								{/if}
							</label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Descripción</span><input class={inputCls} bind:value={newItem.description} /></label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Cantidad</span><input type="number" min="0" step="0.01" class={inputCls} bind:value={newItem.quantity} /></label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Importe unitario</span><input type="number" min="0" step="0.01" class={inputCls} bind:value={newItem.unit_amount} /></label>
							<!-- Claves del SAT de la partida. Vienen del concepto del catálogo cuando lo hay,
							     y se pueden ajustar aquí; sin ellas no se puede timbrar. -->
							<label class="flex flex-col gap-1 text-sm">
								<span class="font-medium">Clave producto/servicio (SAT)</span>
								<select class={inputCls} bind:value={newItem.product_service_id}>
									<option value={null}>Selecciona…</option>
									{#each productsServices as ps (ps.id)}<option value={ps.id}>{ps.code} — {ps.description}</option>{/each}
								</select>
							</label>
							<label class="flex flex-col gap-1 text-sm">
								<span class="font-medium">Clave de unidad (SAT)</span>
								<select class={inputCls} bind:value={newItem.unit_of_measure_id}>
									<option value={null}>Selecciona…</option>
									{#each unitsOfMeasure as u (u.id)}<option value={u.id}>{u.code} — {u.description}</option>{/each}
								</select>
							</label>
							<label class="flex flex-col gap-1 text-sm sm:col-span-2">
								<span class="font-medium">Objeto de impuesto (SAT)</span>
								<select class={inputCls} bind:value={newItem.tax_object_id}>
									<option value={null}>Selecciona…</option>
									{#each taxObjects as o (o.id)}<option value={o.id}>{o.code} — {o.description}</option>{/each}
								</select>
								<span class="text-xs text-muted-foreground">
									Con «02 — Sí objeto de impuesto» el IVA se calcula solo, con el % de la factura.
								</span>
							</label>
							<div class="flex justify-end gap-2 sm:col-span-2"><Button variant="outline" size="sm" onclick={() => (addingItem = false)}>Cancelar</Button><Button size="sm" onclick={saveItem}>Guardar</Button></div>
						</div>
					{/if}
					{#if fiscalItem}
						<!-- Claves del SAT de una partida ya creada: es lo que faltaba para poder timbrar
						     partidas dadas de alta antes de que existieran estos campos. -->
						<div class="mb-4 grid gap-3 rounded-md border p-3 sm:grid-cols-2">
							<p class="text-sm font-medium sm:col-span-2">
								Claves fiscales de «{itemConceptLabel(fiscalItem)}»
							</p>
							<label class="flex flex-col gap-1 text-sm">
								<span class="font-medium">Clave producto/servicio (SAT)</span>
								<select class={inputCls} bind:value={fiscalForm.product_service_id}>
									<option value={null}>Selecciona…</option>
									{#each productsServices as ps (ps.id)}<option value={ps.id}>{ps.code} — {ps.description}</option>{/each}
								</select>
							</label>
							<label class="flex flex-col gap-1 text-sm">
								<span class="font-medium">Clave de unidad (SAT)</span>
								<select class={inputCls} bind:value={fiscalForm.unit_of_measure_id}>
									<option value={null}>Selecciona…</option>
									{#each unitsOfMeasure as u (u.id)}<option value={u.id}>{u.code} — {u.name}</option>{/each}
								</select>
							</label>
							<label class="flex flex-col gap-1 text-sm sm:col-span-2">
								<span class="font-medium">Objeto de impuesto (SAT)</span>
								<select class={inputCls} bind:value={fiscalForm.tax_object_id}>
									<option value={null}>Selecciona…</option>
									{#each taxObjects as o (o.id)}<option value={o.id}>{o.code} — {o.description}</option>{/each}
								</select>
								<span class="text-xs text-muted-foreground">
									Con «02 — Sí objeto de impuesto» el IVA se calcula solo, usando el {invoice.tax_rate}%
									de la factura.
								</span>
							</label>
							<div class="flex justify-end gap-2 sm:col-span-2">
								<Button variant="outline" size="sm" onclick={() => (fiscalItem = null)}>Cancelar</Button>
								<Button size="sm" onclick={saveFiscal} disabled={busy}>Guardar claves</Button>
							</div>
						</div>
					{/if}
					{#if items.length === 0}
						<p class="text-sm text-muted-foreground">Sin conceptos.</p>
					{:else}
						<Table.Root>
							<Table.Header><Table.Row><Table.Head>Concepto</Table.Head><Table.Head>Claves SAT</Table.Head><Table.Head class="text-right">Cant.</Table.Head><Table.Head class="text-right">Unitario</Table.Head><Table.Head class="text-right">Importe</Table.Head><Table.Head></Table.Head></Table.Row></Table.Header>
							<Table.Body>
								{#each items as it (it.id)}
									<Table.Row>
										<Table.Cell class="font-medium">{itemConceptLabel(it)}{#if it.description}<span class="block text-xs text-muted-foreground">{it.description}</span>{/if}</Table.Cell>
										<Table.Cell class="text-xs">
											{#if it.product_service_id && it.unit_of_measure_id && it.tax_object_id}
												<span class="text-emerald-600">completas</span>
											{:else}
												<button type="button" class="text-destructive underline" onclick={() => startFiscal(it)}>faltan claves</button>
											{/if}
										</Table.Cell>
										<Table.Cell class="text-right">{it.quantity}</Table.Cell>
										<Table.Cell class="text-right">{formatMoney(it.unit_amount, invoice.currency)}</Table.Cell>
										<Table.Cell class="text-right">{formatMoney(it.line_total, invoice.currency)}</Table.Cell>
										<Table.Cell class="text-right">
											<Button variant="ghost" size="sm" onclick={() => startFiscal(it)} aria-label="Claves fiscales"><Receipt class="h-4 w-4" /></Button>
											<Button variant="ghost" size="sm" onclick={() => removeItem(it)} aria-label="Eliminar"><Trash2 class="h-4 w-4 text-destructive" /></Button>
										</Table.Cell>
									</Table.Row>
								{/each}
							</Table.Body>
						</Table.Root>
					{/if}
				{:else if tab === 'pagos'}
					<div class="mb-3 flex justify-end"><Button size="sm" variant="outline" onclick={startPay}><Plus class="mr-1 h-4 w-4" /> Registrar pago</Button></div>
					{#if addingPay}
						<div class="mb-4 grid gap-3 rounded-md border p-3 sm:grid-cols-2">
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Monto</span><input type="number" min="0" step="0.01" class={inputCls} bind:value={newPay.amount} /></label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Fecha</span><input type="date" class={inputCls} bind:value={newPay.payment_date} /></label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Método</span><select class={inputCls} bind:value={newPay.method}>{#each PAYMENT_METHODS as m (m.value)}<option value={m.value}>{m.label}</option>{/each}</select></label>
							<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Referencia</span><input class={inputCls} bind:value={newPay.reference} /></label>
							<div class="flex justify-end gap-2 sm:col-span-2"><Button variant="outline" size="sm" onclick={() => (addingPay = false)}>Cancelar</Button><Button size="sm" onclick={savePay}>Guardar</Button></div>
						</div>
					{/if}
					{#if payments.length === 0}
						<p class="text-sm text-muted-foreground">Sin pagos registrados.</p>
					{:else}
						<Table.Root>
							<Table.Header><Table.Row><Table.Head>Fecha</Table.Head><Table.Head>Método</Table.Head><Table.Head>Referencia</Table.Head><Table.Head class="text-right">Monto</Table.Head><Table.Head></Table.Head></Table.Row></Table.Header>
							<Table.Body>
								{#each payments as p (p.id)}
									<Table.Row>
										<Table.Cell>{p.payment_date ?? '—'}</Table.Cell>
										<Table.Cell>{labelOf(PAYMENT_METHODS, p.method)}</Table.Cell>
										<Table.Cell class="font-mono text-xs">{p.reference ?? '—'}</Table.Cell>
										<Table.Cell class="text-right">{formatMoney(p.amount, invoice.currency)}</Table.Cell>
										<Table.Cell class="text-right"><Button variant="ghost" size="sm" onclick={() => removePay(p)} aria-label="Eliminar"><Trash2 class="h-4 w-4 text-destructive" /></Button></Table.Cell>
									</Table.Row>
								{/each}
							</Table.Body>
						</Table.Root>
					{/if}
				{:else}
					<div class="grid gap-4 sm:grid-cols-2">
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Folio</span><input class={inputCls} bind:value={form.reference} /></label>
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Cliente</span><select class={inputCls} bind:value={form.account_id}><option value={undefined}>—</option>{#each accounts as a (a.id)}<option value={a.id}>{a.name}</option>{/each}</select></label>
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Moneda</span><input class={inputCls} maxlength="3" bind:value={form.currency} /></label>
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">% Impuesto</span><input type="number" min="0" max="100" step="0.01" class={inputCls} bind:value={form.tax_rate} /></label>
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Emisión</span><input type="date" class={inputCls} bind:value={form.issue_date} /></label>
						<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Vencimiento</span><input type="date" class={inputCls} bind:value={form.due_date} /></label>
						<!-- Claves fiscales del comprobante. Sin ellas el timbrado se detiene en la
						     validación, antes de llegar al PAC. -->
						<label class="flex flex-col gap-1 text-sm">
							<span class="font-medium">Forma de pago (SAT) *</span>
							<select class={inputCls} bind:value={form.payment_form_id}>
								<option value={null}>Selecciona…</option>
								{#each paymentForms as f (f.id)}<option value={f.id}>{f.code} — {f.description}</option>{/each}
							</select>
							<span class="text-xs text-muted-foreground">Con qué se paga: efectivo, transferencia…</span>
						</label>

						<label class="flex flex-col gap-1 text-sm">
							<span class="font-medium">Método de pago (SAT) *</span>
							<select class={inputCls} bind:value={form.payment_method_id}>
								<option value={null}>Selecciona…</option>
								{#each paymentMethods as m (m.id)}<option value={m.id}>{m.code} — {m.description}</option>{/each}
							</select>
							<span class="text-xs text-muted-foreground">PUE en una exhibición, PPD en parcialidades.</span>
						</label>

						<label class="flex flex-col gap-1 text-sm">
							<span class="font-medium">CP del lugar de expedición</span>
							<input class={inputCls} maxlength="5" inputmode="numeric" bind:value={form.expedition_zip_code} />
							<span class="text-xs text-muted-foreground">Si se deja vacío se usa el del emisor.</span>
						</label>

						<label class="flex flex-col gap-1 text-sm sm:col-span-2">
							<span class="font-medium">Modo de timbrado</span>
							<select class={inputCls} bind:value={form.stamping_mode} disabled={!!stamp}>
								<option value="pruebas">Pruebas — el comprobante no tiene validez fiscal</option>
								<option value="produccion">Producción — emite un CFDI real ante el SAT</option>
							</select>
							<span class="text-xs text-muted-foreground">
								{#if stamp}
									La factura ya está timbrada: el modo no se puede cambiar.
								{:else}
									Determina a qué entorno del PAC se transmite. En producción el comprobante tiene
									validez fiscal y deshacerlo obliga a cancelarlo ante el SAT.
								{/if}
							</span>
						</label>
						<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Datos bancarios</span><textarea rows="2" class={inputCls} bind:value={form.bank_info}></textarea></label>
						<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Notas</span><textarea rows="2" class={inputCls} bind:value={form.notes}></textarea></label>
					</div>
					<div class="mt-6 flex justify-end border-t pt-4"><Button onclick={saveHeader} disabled={saving}>{saving ? 'Guardando…' : 'Guardar cambios'}</Button></div>
				{/if}
			</Card.Content>
		</Card.Root>
	{/if}
</div>
