"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { Ban, Plus, Replace, Truck } from "lucide-react";
import { admiteCaptura } from "@/lib/costos-reglas";
import {
  api,
  type ConsumoAdicional,
  type ConsumosAdicionalesDeOrden,
  type Orden,
  type Producto,
} from "@/lib/api";
import { usePuede } from "@/components/sesion-proveedor";
import { NombreProducto } from "@/components/nombre-producto";
import { leerNombre } from "@/lib/producto";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { toast } from "sonner";

const numero = new Intl.NumberFormat("es-GT", { maximumFractionDigits: 4 });

/** Motivos de un clic. Son sugerencias, no una lista cerrada: el campo sigue siendo libre. */
const MOTIVOS_SUGERIDOS = ["Completar lote", "Merma de arranque", "Reproceso"];

/**
 * La materia prima que se gastó por encima de la fórmula.
 *
 * Autocontenido a propósito: hace sus propias llamadas y se monta en la pantalla de la
 * orden con una línea. Así el archivo grande de la orden no crece y esto se puede mover
 * de lugar sin tocarlo.
 *
 * ⚠️ **Avisa, no frena.** Cuando lo que se pide es más de lo que hay en bodega, se
 * muestra el faltante y se deja seguir: la bodega física manda sobre el saldo del
 * sistema, y mbinv admite existencias negativas por diseño. Frenar acá obligaría a la
 * planta a mentirle al sistema para poder trabajar.
 */
export function ConsumosAdicionales({
  orden,
  onCambio,
  onResumenCambia,
}: {
  orden: Orden;
  onCambio: () => void;
  /**
   * Lo que la pantalla de la orden necesita saber de acá: cuántos renglones quedan sin
   * despachar (para apagar «Registrar») y cuántos ya están en producción (para decirlo
   * en el diálogo de producción, porque se van a gastar).
   */
  onResumenCambia?: (resumen: { pendientes: number; despachados: number; conocido: boolean; fallo: boolean }) => void;
}) {
  const [consumos, setConsumos] = useState<ConsumoAdicional[] | null>(null);
  // Si la lectura falló, el estado es DESCONOCIDO: una lista vacía diría «no hay nada» y dejaría registrar la producción
  // con renglones pendientes que no se alcanzaron a ver.
  const [falloLectura, setFalloLectura] = useState(false);
  const [trabajando, setTrabajando] = useState(false);

  const puedeVer = usePuede("ver");
  const puedePedir = usePuede("registrar_produccion");
  const puedeDespachar = usePuede("despachar_mp");

  // Antes del despacho de materia prima no existe el concepto: no hay bodega de
  // producción con nada adentro a lo que agregarle.
  const yaDespachada = orden.estado !== "BORRADOR" && orden.estado !== "PLANIFICADA";
  // El servidor admite agregar, despachar y anular con la materia prima despachada Y en ejecución (`admiteCaptura`): capturar la
  // primera producción pasa la orden a EN_EJECUCION y no puede dejar esta tarjeta sin acciones.
  const soloLectura = !admiteCaptura(orden.estado);

  const cargar = useCallback(async () => {
    if (!yaDespachada || !puedeVer) return;
    try {
      const datos = await api<ConsumosAdicionalesDeOrden>(
        `/api/ordenes/${orden.id}/consumos-adicionales`,
      );
      setConsumos(datos.consumos);
      setFalloLectura(false);
    } catch (error) {
      toast.error(
        error instanceof Error ? error.message : "No se pudo leer la materia prima adicional.",
      );
      setFalloLectura(true);
    }
    // ⚠️ `orden.estado` entre las dependencias, y no es adorno: al registrar la
    // producción el servidor marca estos renglones como CONSUMIDO y les pone su
    // documento, pero eso pasa del otro lado. Sin esta dependencia la tarjeta se queda
    // diciendo «Despachado» sobre materia prima que ya se gastó —medido en la demo el
    // 14-oct-2026— y el renglón parece pendiente para siempre.
  }, [orden.id, orden.estado, yaDespachada, puedeVer]);

  useEffect(() => {
    void cargar();
  }, [cargar]);

  const pendientes = useMemo(
    () => (consumos ?? []).filter((c) => c.estado === "PENDIENTE"),
    [consumos],
  );
  const despachados = useMemo(
    () => (consumos ?? []).filter((c) => c.estado === "DESPACHADO").length,
    [consumos],
  );

  // Las dependencias son números, no el objeto: así no se dispara en cada render.
  useEffect(() => {
    onResumenCambia?.({ pendientes: pendientes.length, despachados, conocido: !(yaDespachada && puedeVer) || (null !== consumos && !falloLectura), fallo: falloLectura });
  }, [pendientes.length, despachados, onResumenCambia, yaDespachada, puedeVer, consumos, falloLectura]);

  /**
   * Toda acción contesta la lista entera, así que no hay que adivinar el estado nuevo:
   * se reemplaza con lo que dijo el servidor. Es lo que hace que dos personas trabajando
   * a la vez converjan en vez de pelearse.
   */
  async function ejecutar(ruta: string, cuerpo: unknown, exito: string) {
    setTrabajando(true);
    try {
      const datos = await api<ConsumosAdicionalesDeOrden>(
        `/api/ordenes/${orden.id}/consumos-adicionales${ruta}`,
        { metodo: "POST", cuerpo },
      );
      setConsumos(datos.consumos);
      toast.success(exito);
      onCambio();
      return true;
    } catch (error) {
      // Los mensajes del servidor están redactados para quien está frente a la pantalla
      // y dicen qué hacer en su lugar. Se muestran enteros y con tiempo para leerlos.
      const mensaje = error instanceof Error ? error.message : "No se pudo completar la operación.";
      if ("/despachar" === ruta && mensaje.startsWith("No hay consumos adicionales pendientes")) {
        // Otra persona ya despachó lo mismo: es la convergencia esperada, no un fallo.
        toast.info("Otra persona ya despachó estos consumos. Se actualizó la lista.");
      } else {
        toast.error(mensaje, { duration: 10000 });
      }
      // Pudo fallar porque alguien más ya lo hizo: releer deja la pantalla en la verdad.
      void cargar();
      return false;
    } finally {
      setTrabajando(false);
    }
  }

  if (!yaDespachada || !puedeVer) return null;

  // Terminada o anulada y sin consumos: no hay nada que contar, y una tarjeta vacía
  // sólo estorba al que viene a leer la orden.
  if (soloLectura && !falloLectura && (consumos === null || consumos.length === 0)) return null;

  // El aviso es para decidir si despachar. En una orden ya cerrada el despacho ya pasó,
  // así que el consejo sobra y sólo ensucia la lectura.
  const hayFaltante =
    !soloLectura && (consumos ?? []).some((c) => c.hayFaltante && c.estado !== "ANULADO");

  return (
    <Card className="min-w-0">
      <CardHeader>
        <div className="flex flex-wrap items-start justify-between gap-3">
          <div className="min-w-0">
            <CardTitle className="text-base">Materia prima adicional</CardTitle>
            <CardDescription>
              Lo que la planta gastó por encima de la fórmula para completar el lote. Se
              suma al costo del producto.
            </CardDescription>
          </div>
          {!soloLectura ? (
            <div className="flex flex-wrap gap-2">
              {pendientes.length > 0 && puedeDespachar && null !== consumos && !falloLectura ? (
                <DialogoDespachar
                  orden={orden}
                  pendientes={pendientes.length}
                  trabajando={trabajando}
                  ejecutar={ejecutar}
                />
              ) : null}
              {/* Sin la lista cargada no se sabe qué ya existe: agregar a ciegas duplicaría un renglón. */}
              {puedePedir && null !== consumos && !falloLectura ? (
                <DialogoAgregar orden={orden} trabajando={trabajando} ejecutar={ejecutar} />
              ) : null}
            </div>
          ) : null}
        </div>
      </CardHeader>

      <CardContent>
        {falloLectura ? (
          <p className="py-6 text-center text-sm text-amber-600">
            No se pudo leer la materia prima adicional, así que no se sabe si hay renglones pendientes.
            Recargue la página antes de registrar la producción.
          </p>
        ) : consumos === null ? (
          <p className="py-6 text-center text-sm text-muted-foreground">Cargando…</p>
        ) : consumos.length === 0 ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            Nada por encima de la fórmula. Si la planta gastó de más, agregalo acá para
            que el costo del producto lo refleje.
          </p>
        ) : (
          <>
            {hayFaltante ? (
              <p className="mb-3 text-sm text-amber-600">
                Hay renglones que piden más de lo que había en la bodega de materia prima.
                El despacho se puede hacer igual: la bodega física manda sobre el saldo
                del sistema.
              </p>
            ) : null}

            {/* Escritorio */}
            <div className="hidden sm:block">
              <Table>
                <TableHeader>
                  <TableRow>
                    <TableHead>Producto</TableHead>
                    <TableHead>Materia prima</TableHead>
                    <TableHead className="text-right">Cantidad</TableHead>
                    <TableHead>Motivo</TableHead>
                    <TableHead>Estado</TableHead>
                    <TableHead />
                  </TableRow>
                </TableHeader>
                <TableBody>
                  {consumos.map((c) => (
                    <TableRow key={c.id} className={c.estado === "ANULADO" ? "opacity-60" : ""}>
                      <TableCell className="text-sm">
                        {leerNombre(nombreDelProducto(orden, c.pluProducto)).nombre}
                      </TableCell>
                      <TableCell>
                        <NombreProducto plu={c.pluInsumo} descripcion={c.descripcionInsumo} />
                      </TableCell>
                      <TableCell className="text-right tabular-nums">
                        <span className={c.estado === "ANULADO" ? "line-through" : ""}>
                          {numero.format(c.cantidad)}
                        </span>
                        {c.hayFaltante && c.estado !== "ANULADO" ? (
                          <div className="text-xs text-amber-600">
                            había {numero.format(c.existenciaAlAgregar)}
                          </div>
                        ) : null}
                      </TableCell>
                      <TableCell className="max-w-48 text-sm text-muted-foreground">
                        <span className="line-clamp-2">{c.motivo}</span>
                        {c.anuladoMotivo ? (
                          <span className="block text-xs italic">
                            Anulado: {c.anuladoMotivo}
                          </span>
                        ) : null}
                      </TableCell>
                      <TableCell>
                        <EstadoConsumo consumo={c} />
                      </TableCell>
                      <TableCell className="text-right">
                        <Acciones
                          orden={orden}
                          consumo={c}
                          soloLectura={soloLectura}
                          puedePedir={puedePedir}
                          trabajando={trabajando}
                          ejecutar={ejecutar}
                        />
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </div>

            {/* Teléfono: la tabla no cabe en 375 px, así que cada renglón es una tarjeta.
                Se verificó que el módulo no tenga desplazamiento horizontal. */}
            <div className="space-y-3 sm:hidden">
              {consumos.map((c) => (
                <div
                  key={c.id}
                  className={`rounded-lg border p-3 ${c.estado === "ANULADO" ? "opacity-60" : ""}`}
                >
                  <div className="flex items-start justify-between gap-2">
                    <NombreProducto plu={c.pluInsumo} descripcion={c.descripcionInsumo} />
                    <EstadoConsumo consumo={c} />
                  </div>
                  <div className="mt-2 flex items-baseline justify-between gap-2 text-sm">
                    <span className="text-muted-foreground">
                      para {leerNombre(nombreDelProducto(orden, c.pluProducto)).nombre}
                    </span>
                    <span
                      className={`tabular-nums font-medium ${c.estado === "ANULADO" ? "line-through" : ""}`}
                    >
                      {numero.format(c.cantidad)}
                    </span>
                  </div>
                  {c.hayFaltante && c.estado !== "ANULADO" ? (
                    <p className="mt-1 text-xs text-amber-600">
                      había {numero.format(c.existenciaAlAgregar)} en bodega
                    </p>
                  ) : null}
                  <p className="mt-1 text-sm text-muted-foreground">{c.motivo}</p>
                  {c.anuladoMotivo ? (
                    <p className="text-xs italic text-muted-foreground">
                      Anulado: {c.anuladoMotivo}
                    </p>
                  ) : null}
                  <div className="mt-2">
                    <Acciones
                      orden={orden}
                      consumo={c}
                      soloLectura={soloLectura}
                      puedePedir={puedePedir}
                      trabajando={trabajando}
                      ejecutar={ejecutar}
                    />
                  </div>
                </div>
              ))}
            </div>
          </>
        )}
      </CardContent>
    </Card>
  );
}

/** El nombre del producto de la orden al que se le carga el consumo. */
function nombreDelProducto(orden: Orden, plu: string): string {
  return orden.detalles.find((d) => d.plu === plu)?.descripcion ?? plu;
}

/**
 * El estado, dicho en lo que significa para la planta y no en el nombre de la columna.
 *
 * «Pendiente» no es un adorno de proceso: quiere decir que esa materia prima todavía
 * está en la bodega de materia prima y hay que traerla, y que mientras tanto no se
 * puede registrar la producción.
 */
function EstadoConsumo({ consumo }: { consumo: ConsumoAdicional }) {
  const estilos: Record<ConsumoAdicional["estado"], string> = {
    PENDIENTE: "bg-amber-100 text-amber-800 dark:bg-amber-950 dark:text-amber-300",
    DESPACHADO: "bg-blue-100 text-blue-800 dark:bg-blue-950 dark:text-blue-300",
    CONSUMIDO: "bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300",
    ANULADO: "bg-muted text-muted-foreground",
  };

  const dice: Record<ConsumoAdicional["estado"], string> = {
    PENDIENTE: "Falta traerla a producción",
    DESPACHADO: "En producción, se gasta al registrar",
    CONSUMIDO: consumo.consumoDocumento
      ? `Gastada, doc. ${consumo.consumoDocumento}`
      : "Gastada",
    ANULADO: "Anulado",
  };

  const etiqueta: Record<ConsumoAdicional["estado"], string> = {
    PENDIENTE: "Pendiente",
    DESPACHADO: "Despachado",
    CONSUMIDO: "Consumido",
    ANULADO: "Anulado",
  };

  return (
    <div className="min-w-0">
      <span
        className={`inline-flex rounded px-2 py-0.5 text-xs font-medium ${estilos[consumo.estado]}`}
      >
        {etiqueta[consumo.estado]}
      </span>
      <div className="mt-0.5 text-xs text-muted-foreground">{dice[consumo.estado]}</div>
    </div>
  );
}

function Acciones({
  orden,
  consumo,
  soloLectura,
  puedePedir,
  trabajando,
  ejecutar,
}: {
  orden: Orden;
  consumo: ConsumoAdicional;
  soloLectura: boolean;
  puedePedir: boolean;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  // Esconder un botón no protege nada —el servidor vuelve a verificar cada permiso—;
  // es para no ofrecer lo que va a contestar 403.
  if (soloLectura || !puedePedir) return null;

  const cerrado = consumo.estado === "CONSUMIDO" || consumo.estado === "ANULADO";
  const puedeReasignar = orden.detalles.length > 1 && !cerrado;

  if (consumo.estado !== "PENDIENTE" && !puedeReasignar) return null;

  return (
    <div className="flex flex-wrap justify-end gap-1">
      {puedeReasignar ? (
        <DialogoReasignar
          orden={orden}
          consumo={consumo}
          trabajando={trabajando}
          ejecutar={ejecutar}
        />
      ) : null}
      {consumo.estado === "PENDIENTE" ? (
        <DialogoAnularConsumo consumo={consumo} trabajando={trabajando} ejecutar={ejecutar} />
      ) : null}
    </div>
  );
}

function DialogoAgregar({
  orden,
  trabajando,
  ejecutar,
}: {
  orden: Orden;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  const [abierto, setAbierto] = useState(false);
  // Con un solo producto no hay nada que elegir: se preselecciona y el campo informa.
  const [pluProducto, setPluProducto] = useState(
    orden.detalles.length === 1 ? orden.detalles[0].plu : "",
  );
  const [busqueda, setBusqueda] = useState("");
  const [resultados, setResultados] = useState<Producto[]>([]);
  const [insumo, setInsumo] = useState<Producto | null>(null);
  const [cantidad, setCantidad] = useState("");
  const [motivo, setMotivo] = useState("");
  const [yaEstaEnProduccion, setYaEstaEnProduccion] = useState(false);

  // El mismo buscador con espera que usa «nueva orden»: sin los 250 ms se dispara una
  // consulta por tecla contra el catálogo del cliente.
  useEffect(() => {
    if (busqueda.trim().length < 2) {
      setResultados([]);
      return;
    }
    // Una respuesta vieja no debe pisar la de la búsqueda actual ni resucitar tras limpiar().
    let vigente = true;
    const temporizador = setTimeout(() => {
      api<Producto[]>(`/api/catalogo/productos?q=${encodeURIComponent(busqueda)}`)
        .then((r) => {
          if (vigente) setResultados(r);
        })
        .catch(() => {
          if (vigente) setResultados([]);
        });
    }, 250);
    return () => {
      vigente = false;
      clearTimeout(temporizador);
    };
  }, [busqueda]);

  function limpiar() {
    setPluProducto(orden.detalles.length === 1 ? orden.detalles[0].plu : "");
    setBusqueda("");
    setResultados([]);
    setInsumo(null);
    setCantidad("");
    setMotivo("");
    setYaEstaEnProduccion(false);
  }

  const listo =
    pluProducto !== "" && insumo !== null && Number(cantidad) > 0 && motivo.trim() !== "";

  return (
    <Dialog
      open={abierto}
      onOpenChange={(v) => {
        setAbierto(v);
        if (!v) limpiar();
      }}
    >
      <DialogTrigger asChild>
        <Button size="sm" variant="outline" disabled={trabajando}>
          <Plus className="size-4" />
          Agregar materia prima
        </Button>
      </DialogTrigger>
      <DialogContent className="max-h-[90vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Agregar materia prima</DialogTitle>
          <DialogDescription>
            Lo que se gastó de más y la fórmula no contemplaba. Se le carga al producto
            que elijas, para que su costo lo refleje.
          </DialogDescription>
        </DialogHeader>

        <div className="space-y-4">
          <div className="space-y-2">
            <Label htmlFor="ca-producto">¿A qué producto se le carga?</Label>
            {orden.detalles.length === 1 ? (
              <p className="text-sm text-muted-foreground">
                {leerNombre(orden.detalles[0].descripcion ?? orden.detalles[0].plu).nombre}
              </p>
            ) : (
              <Select value={pluProducto} onValueChange={setPluProducto}>
                <SelectTrigger id="ca-producto">
                  <SelectValue placeholder="Elegí el producto" />
                </SelectTrigger>
                <SelectContent>
                  {orden.detalles.map((d) => (
                    <SelectItem key={d.plu} value={d.plu}>
                      {leerNombre(d.descripcion ?? d.plu).nombre}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="ca-insumo">¿Qué materia prima?</Label>
            {insumo ? (
              <div className="flex items-start justify-between gap-2 rounded-md border p-2">
                <NombreProducto plu={insumo.plu} descripcion={insumo.descripcion} />
                <Button variant="ghost" size="sm" onClick={() => setInsumo(null)}>
                  Cambiar
                </Button>
              </div>
            ) : (
              <>
                <Input
                  id="ca-insumo"
                  value={busqueda}
                  onChange={(e) => setBusqueda(e.target.value)}
                  placeholder="Buscar por nombre o código…"
                />
                {resultados.length > 0 ? (
                  <div className="max-h-48 overflow-y-auto rounded-md border">
                    {resultados.map((p) => (
                      <button
                        key={p.plu}
                        type="button"
                        className="flex w-full items-start gap-2 border-b p-2 text-left last:border-b-0 hover:bg-muted"
                        onClick={() => {
                          setInsumo(p);
                          setBusqueda("");
                          setResultados([]);
                        }}
                      >
                        <NombreProducto plu={p.plu} descripcion={p.descripcion} />
                      </button>
                    ))}
                  </div>
                ) : null}
              </>
            )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="ca-cantidad">¿Cuánta?</Label>
            <Input
              id="ca-cantidad"
              type="number"
              min={0}
              step="any"
              value={cantidad}
              onChange={(e) => setCantidad(e.target.value)}
            />
          </div>

          <div className="space-y-2">
            <Label htmlFor="ca-motivo">¿Por qué se gastó de más?</Label>
            <Textarea
              id="ca-motivo"
              value={motivo}
              maxLength={255}
              onChange={(e) => setMotivo(e.target.value)}
              placeholder="Queda guardado en la orden y en la bitácora."
            />
            <div className="flex flex-wrap gap-1">
              {MOTIVOS_SUGERIDOS.map((m) => (
                <Button key={m} type="button" variant="outline" size="sm" onClick={() => setMotivo(m)}>
                  {m}
                </Button>
              ))}
            </div>
          </div>

          <div className="flex items-start gap-2 rounded-md border p-3">
            <Checkbox
              id="ca-sobrante"
              checked={yaEstaEnProduccion}
              onCheckedChange={(v) => setYaEstaEnProduccion(v === true)}
            />
            <div className="space-y-1">
              <Label htmlFor="ca-sobrante" className="font-normal">
                Ya está en la bodega de producción (sobrante)
              </Label>
              <p className="text-xs text-muted-foreground">
                Marcalo si esa materia prima ya estaba ahí de un lote anterior: entonces
                no hay que traerla, sólo contarla.
              </p>
            </div>
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando || !listo}
            onClick={async () => {
              const bien = await ejecutar(
                "",
                {
                  pluProducto,
                  pluInsumo: insumo?.plu,
                  cantidad: Number(cantidad),
                  motivo: motivo.trim(),
                  requiereDespacho: !yaEstaEnProduccion,
                },
                "Materia prima adicional agregada.",
              );
              if (bien) setAbierto(false);
            }}
          >
            Agregar
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DialogoDespachar({
  orden,
  pendientes,
  trabajando,
  ejecutar,
}: {
  orden: Orden;
  pendientes: number;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  const [abierto, setAbierto] = useState(false);

  return (
    <Dialog open={abierto} onOpenChange={setAbierto}>
      <DialogTrigger asChild>
        <Button size="sm" disabled={trabajando}>
          <Truck className="size-4" />
          Despachar lo pendiente
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Traer la materia prima a producción</DialogTitle>
          <DialogDescription>
            Se hará <strong>un</strong> traslado de{" "}
            {orden.bodegaMateriaPrimaNombre ?? `bodega ${orden.bodegaMateriaPrima}`} a{" "}
            {orden.bodegaProduccionNombre ?? `bodega ${orden.bodegaProduccion}`} con{" "}
            {pendientes} {pendientes === 1 ? "renglón" : "renglones"}. Después de esto se
            puede registrar la producción.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando}
            onClick={async () => {
              const bien = await ejecutar("/despachar", {}, "Materia prima adicional despachada.");
              if (bien) setAbierto(false);
            }}
          >
            Despachar
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DialogoAnularConsumo({
  consumo,
  trabajando,
  ejecutar,
}: {
  consumo: ConsumoAdicional;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  const [abierto, setAbierto] = useState(false);
  const [motivo, setMotivo] = useState("");

  return (
    <Dialog
      open={abierto}
      onOpenChange={(v) => {
        setAbierto(v);
        if (!v) setMotivo("");
      }}
    >
      <DialogTrigger asChild>
        <Button size="sm" variant="ghost" disabled={trabajando}>
          <Ban className="size-4" />
          Anular
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Anular este renglón</DialogTitle>
          <DialogDescription>
            Todavía no se despachó, así que no hay inventario que revertir: el renglón
            queda anulado y deja de contar para el costo.
          </DialogDescription>
        </DialogHeader>
        <div className="space-y-2">
          <Label htmlFor={`ca-anular-${consumo.id}`}>¿Por qué se anula?</Label>
          <Textarea
            id={`ca-anular-${consumo.id}`}
            value={motivo}
            maxLength={255}
            onChange={(e) => setMotivo(e.target.value)}
            placeholder="Queda guardado en la bitácora."
          />
        </div>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            variant="destructive"
            disabled={trabajando || motivo.trim() === ""}
            onClick={async () => {
              const bien = await ejecutar(
                `/${consumo.id}/anular`,
                { motivo: motivo.trim() },
                "Renglón anulado.",
              );
              if (bien) setAbierto(false);
            }}
          >
            Anular
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function DialogoReasignar({
  orden,
  consumo,
  trabajando,
  ejecutar,
}: {
  orden: Orden;
  consumo: ConsumoAdicional;
  trabajando: boolean;
  ejecutar: (ruta: string, cuerpo: unknown, exito: string) => Promise<boolean>;
}) {
  const [abierto, setAbierto] = useState(false);
  const [pluProducto, setPluProducto] = useState(consumo.pluProducto);

  return (
    <Dialog
      open={abierto}
      onOpenChange={(v) => {
        setAbierto(v);
        if (!v) setPluProducto(consumo.pluProducto);
      }}
    >
      <DialogTrigger asChild>
        <Button size="sm" variant="ghost" disabled={trabajando}>
          <Replace className="size-4" />
          Cambiar producto
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>¿A qué producto se le carga?</DialogTitle>
          <DialogDescription>
            Cambia a cuál de los productos de la orden se le suma este costo. No mueve
            inventario: la materia prima es la misma y está donde estaba.
          </DialogDescription>
        </DialogHeader>
        <Select value={pluProducto} onValueChange={setPluProducto}>
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {orden.detalles.map((d) => (
              <SelectItem key={d.plu} value={d.plu}>
                {leerNombre(d.descripcion ?? d.plu).nombre}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
        <DialogFooter>
          <Button variant="outline" onClick={() => setAbierto(false)}>
            Cancelar
          </Button>
          <Button
            disabled={trabajando || pluProducto === consumo.pluProducto}
            onClick={async () => {
              const bien = await ejecutar(
                `/${consumo.id}/reasignar`,
                { pluProducto },
                "Renglón reasignado.",
              );
              if (bien) setAbierto(false);
            }}
          >
            Guardar
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
