import { Icon } from "@iconify/react";
import { AsientosEjemploPanel } from "./AsientosEjemploPanel";
import { MediosPagoMapeoCard } from "./MediosPagoMapeoCard";
import { Alert, Button, Chip, MenuItem, TextField, Tooltip } from "@mui/material";
import { CuentaMapeoAutocomplete } from "./CuentaMapeoAutocomplete";
import { ReglasConceptoPanel } from "./ReglasConceptoPanel";
import { CONCEPTOS_MAPEO } from "./conceptosMapeo";
import { VistaPreviaDocumentoDialog } from "./VistaPreviaDocumentoDialog";
import { useMemo, useState } from "react";
import { usePermission } from "../../hooks/usePermission";
import { useSubmodulo } from "../../hooks/useSubmodulo";
import { useAuthStore } from "../../store/AuthStore";
import styled from "styled-components";
import { toast } from "sonner";
import {
  esCuentaLegacy,
  useActualizarMapeoMutation,
  useConfirmarMapeoMutation,
  useMapeoCuentasQuery,
  usePlanCuentasQuery,
  useRecomendacionesMapeoQuery,
  useSembrarMapeosFaltantesMutation,
} from "../../tanstack/ContabilidadStack";

const CONCEPTOS = CONCEPTOS_MAPEO;

// Valor de `localMap`/`currentMap` para los conceptos en modo "sumar a la cuenta
// principal": no apuntan a una cuenta propia, el monto va a la del costo o gasto.
const SUMAR = "__SUMAR__";

// Tono del estado de cada concepto → color semántico del kit (MUI palette).
const COLOR_ESTADO = { error: "error", alerta: "warning", info: "info", ok: "success" };

const AVISO_DOBLE_INGRESO =
  "La factura del exterior y el pago al proveedor van a la misma cuenta: el costo del proveedor entraría dos veces. " +
  "Elegí un esquema: (a) el pago a Importaciones en curso y la factura a otra cuenta, o (b) la factura a Importaciones " +
  "en curso y el pago a la cuenta del proveedor del exterior.";

/** Cómo está hoy el concepto según lo guardado: sin fila, precargado sin confirmar o confirmado. */
function estadoDelConcepto(fila) {
  if (!fila) return { texto: "Sin mapear", tono: "error" };
  if (!fila.confirmado_at) return { texto: "Cargado por el sistema — sin confirmar", tono: "alerta" };
  const quien = fila.confirmado_por_nombre ?? "—";
  if (fila.distinto_de_recomendacion) return { texto: `Confirmado por ${quien} · distinto de lo recomendado`, tono: "info" };
  return { texto: `Confirmado por ${quien}`, tono: "ok" };
}

function flattenCuentas(cuentas, depth = 0) {
  const result = [];
  for (const c of cuentas) {
    result.push({ ...c, depth });
    if (c.children?.length) result.push(...flattenCuentas(c.children, depth + 1));
  }
  return result;
}

export function MapeoCuentasTab() {
  const { can } = usePermission("CONTABILIDAD");
  const canConfig = can("CONT_MAP_MAPEO_EDITAR");
  const hasModulePlan = useAuthStore((s) => s.hasModulePlan);
  // Solo mostramos grupos cuyo módulo esté activo en el plan de la empresa
  // (los grupos sin `modulo` son de contabilidad base y se muestran siempre).
  // Un grupo puede pedir un submódulo del plan (más fino que el módulo). Hoy sólo
  // Comisiones lo usa; si se suman más, conviene pasar esto a un mapa.
  const { hasAccess: planIncluyeComisiones } = useSubmodulo("COB_COMISIONES");
  const submodulosDelPlan = useMemo(
    () => ({ COB_COMISIONES: planIncluyeComisiones }),
    [planIncluyeComisiones],
  );
  const gruposCatalogo = useMemo(
    () =>
      CONCEPTOS.filter((g) => {
        if (g.submodulo) return !!submodulosDelPlan[g.submodulo];
        return !g.modulo || hasModulePlan(g.modulo);
      }),
    [hasModulePlan, submodulosDelPlan],
  );
  const { data: mapeos = [], isLoading: loadingMapeos } = useMapeoCuentasQuery();
  const { data: planCuentas = [], isLoading: loadingPlan } = usePlanCuentasQuery();
  const { data: recomendaciones = {} } = useRecomendacionesMapeoQuery();
  const actualizarMutation = useActualizarMapeoMutation();
  const confirmarMutation = useConfirmarMapeoMutation();
  const sembrarMutation = useSembrarMapeosFaltantesMutation();

  const [localMap, setLocalMap] = useState({});
  // Desde el dibujo del asiento se salta al concepto que falta mapear; queda
  // resaltado unos segundos porque la fila puede caer lejos en la grilla.
  const [conceptoResaltado, setConceptoResaltado] = useState(null);
  const [vistaPrevia, setVistaPrevia] = useState(false);

  const irAConcepto = (concepto) => {
    if (!concepto) return;
    document.getElementById(`concepto-${concepto}`)?.scrollIntoView({ behavior: "smooth", block: "center" });
    setConceptoResaltado(concepto);
    setTimeout(() => setConceptoResaltado((actual) => (actual === concepto ? null : actual)), 3000);
  };

  // Nombre legible de cada concepto: en el dibujo del asiento no puede aparecer
  // "IVA_DEBITO_10" sino "IVA débito fiscal 10%".
  const etiquetasConcepto = useMemo(
    () => Object.fromEntries(CONCEPTOS.flatMap((g) => g.items.map((i) => [i.key, i.label]))),
    [],
  );

  const flatCuentas = useMemo(() => {
    const aceptan = flattenCuentas(planCuentas).filter(c => c.acepta_movimientos && !esCuentaLegacy(c));
    return aceptan;
  }, [planCuentas]);

  // Un mapeo viejo puede apuntar a una cuenta que ya no es elegible: dada de
  // baja, del plan reemplazado (OLD-) o de agrupación. Esas ni siquiera vienen
  // en el árbol del plan, así que el nombre se saca del propio mapeo, que lo
  // trae. Sin esto la fila mostraba el punto verde de "mapeado" con el campo en
  // blanco, sin decir a qué apuntaba ni que hay que arreglarlo: es el caso de
  // las comisiones de DOBA, que por eso no se contabilizan.
  const cuentaDelMapeo = useMemo(
    () => new Map(mapeos.map((m) => [m.concepto, m.cuenta])),
    [mapeos],
  );

  const mapeosPorConcepto = useMemo(() => new Map(mapeos.map((m) => [m.concepto, m])), [mapeos]);

  // Lo guardado: cuenta elegida o SUMAR para los que suman a la cuenta principal.
  const mapaGuardado = useMemo(() => {
    const base = {};
    for (const m of mapeos) base[m.concepto] = m.modo === "SUMAR_A_PRINCIPAL" ? SUMAR : m.cuenta_id;
    return base;
  }, [mapeos]);

  const currentMap = useMemo(() => ({ ...mapaGuardado, ...localMap }), [mapaGuardado, localMap]);

  // Solo viaja lo que el contador tocó: el backend registra cada concepto guardado
  // como confirmado a su nombre, así que reenviar todo confirmaría lo que no revisó.
  // Un valor vacío (p. ej. pasar de "Sumar" a "Una cuenta" sin elegir cuenta) no es
  // un cambio: el backend no borra mapeos, así que habilitar "Guardar" sería mentir.
  // La fila lo avisa con un texto de ayuda (ver `faltaElegirCuenta`).
  const cambios = useMemo(
    () => Object.entries(localMap).filter(([concepto, valor]) => !!valor && valor !== (mapaGuardado[concepto] ?? null)),
    [localMap, mapaGuardado],
  );
  const dirty = cambios.length > 0;

  const sinConfirmar = useMemo(() => mapeos.filter((m) => !m.confirmado_at).length, [mapeos]);

  const dobleIngresoImportacion =
    !!currentMap.IMPORTACION_COMPRA &&
    currentMap.IMPORTACION_COMPRA !== SUMAR &&
    currentMap.IMPORTACION_COMPRA === currentMap.IMPORTACION_PAGO_PROVEEDOR;

  // El catálogo de arriba está escrito a mano, así que se desfasa apenas alguien agrega
  // un concepto en el backend: quedaba mapeado en la base pero sin forma de verlo ni
  // editarlo desde acá. Este grupo recoge todo lo que el backend devuelve y el catálogo
  // no contempla, para que ningún concepto vuelva a quedar invisible.
  const gruposVisibles = useMemo(() => {
    // Se compara contra TODOS los conceptos del catálogo, no solo los visibles, para no
    // mostrar acá lo que está oculto a propósito por no tener el módulo contratado.
    const todosDelCatalogo = new Set(CONCEPTOS.flatMap((g) => g.items.map((i) => i.key)));
    const huerfanos = mapeos
      .map((m) => m.concepto)
      .filter((c) => c && !todosDelCatalogo.has(c))
      .sort();

    if (huerfanos.length === 0) return gruposCatalogo;
    return [
      ...gruposCatalogo,
      {
        grupo: "Otros conceptos",
        items: huerfanos.map((key) => ({
          key,
          // Sin etiqueta curada: se arma una legible a partir del código.
          label: key.toLowerCase().replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase()),
          icon: "lucide:circle-dot",
        })),
      },
    ];
  }, [gruposCatalogo, mapeos]);

  const handleChange = (concepto, cuentaId) => {
    setLocalMap(prev => ({ ...prev, [concepto]: cuentaId }));
  };

  /** "Una cuenta" vuelve a la cuenta guardada (si la había); "Sumar" deja el concepto sin cuenta propia. */
  const handleModo = (concepto, modo) => {
    if (modo === "SUMAR_A_PRINCIPAL") return handleChange(concepto, SUMAR);
    const guardado = mapaGuardado[concepto];
    handleChange(concepto, guardado && guardado !== SUMAR ? guardado : null);
  };

  /** Pone la cuenta recomendada (por código) en el selector; sin código, el modo "sumar". */
  const usarRecomendacion = (concepto, codigo, conModo) => {
    if (!codigo) {
      if (conModo) handleChange(concepto, SUMAR);
      return;
    }
    const cuenta = flatCuentas.find((c) => c.codigo === codigo);
    if (!cuenta) {
      toast.error(`La cuenta ${codigo} no está en el plan o no acepta movimientos`);
      return;
    }
    handleChange(concepto, cuenta.id);
  };

  const handleGuardar = () => {
    // Un concepto vaciado no viaja: el backend no borra mapeos y exige cuenta.
    const payload = cambios
      .filter(([, valor]) => valor)
      .map(([concepto, valor]) =>
        valor === SUMAR
          ? { concepto, cuenta_id: null, modo: "SUMAR_A_PRINCIPAL" }
          : { concepto, cuenta_id: valor, modo: "CUENTA" },
      );
    if (payload.length === 0) {
      setLocalMap({});
      return;
    }
    actualizarMutation.mutate(payload, {
      onSuccess: () => setLocalMap({}),
    });
  };

  const isLoading = loadingMapeos || loadingPlan;

  if (isLoading) {
    return (
      <LoadingWrap>
        <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
        Cargando configuración...
      </LoadingWrap>
    );
  }

  if (planCuentas.length === 0) {
    return (
      <EmptyState>
        <Icon icon="lucide:list-tree" />
        <strong>Primero cargá el Plan de Cuentas</strong>
        <p>Podés crear el plan estándar y los mapeos del sistema automáticamente.</p>
        {canConfig && (
          <SeedBtn
            onClick={() => sembrarMutation.mutate()}
            disabled={sembrarMutation.isPending}
          >
            {sembrarMutation.isPending
              ? <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
              : <Icon icon="lucide:sparkles" />}
            Crear cuentas y mapeos faltantes
          </SeedBtn>
        )}
      </EmptyState>
    );
  }

  return (
    <Wrapper>
      <TopBar>
        <TitleBlock>
          <Icon icon="lucide:git-merge" />
          <div>
            <h2>Mapeo de Cuentas Contables</h2>
            <p>Asigná qué cuenta del plan corresponde a cada concepto del sistema. Esto permite la generación automática de asientos.</p>
          </div>
        </TitleBlock>
        <ActionsRow>
          {/* Ver el mapeo no requiere poder editarlo: probar con un documento no escribe nada. */}
          <Tooltip title="Elegí un documento real y mirá su asiento vigente al lado del que saldría hoy con el mapeo y las excepciones">
            <Button
              variant="outlined"
              onClick={() => setVistaPrevia(true)}
              startIcon={<Icon icon="lucide:file-search" width={16} />}
              sx={{ textTransform: "none", fontWeight: 600, borderRadius: "8px" }}
            >
              Probar con un documento
            </Button>
          </Tooltip>
          {canConfig && (
            <>
              <Tooltip title="Crea cuentas estándar faltantes y asigna mapeos default solo para conceptos del sistema sin cuenta. No sobrescribe los existentes.">
                <SeedBtn
                  onClick={() => sembrarMutation.mutate()}
                  disabled={sembrarMutation.isPending || dirty}
                >
                  {sembrarMutation.isPending
                    ? <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
                    : <Icon icon="lucide:sparkles" />}
                  Crear cuentas y mapeos faltantes
                </SeedBtn>
              </Tooltip>
              <SaveBtn onClick={handleGuardar} disabled={!dirty || actualizarMutation.isPending}>
                {actualizarMutation.isPending
                  ? <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
                  : <Icon icon="lucide:save" />}
                Guardar cambios
              </SaveBtn>
            </>
          )}
        </ActionsRow>
      </TopBar>
      {vistaPrevia && <VistaPreviaDocumentoDialog open onClose={() => setVistaPrevia(false)} />}

      {/* Lo que precargó el sistema no queda como decisión de nadie hasta que el
          contador lo confirma: la elección se registra a su nombre. */}
      {sinConfirmar > 0 && (
        <Alert
          severity="warning"
          icon={<Icon icon="lucide:clipboard-check" />}
          sx={{ mb: 2, alignItems: "center" }}
          action={
            canConfig && (
              <Tooltip title={dirty ? "Primero guardá los cambios" : ""}>
                <span>
                  <Button
                    color="warning"
                    variant="contained"
                    size="small"
                    disableElevation
                    onClick={() => confirmarMutation.mutate()}
                    disabled={dirty || confirmarMutation.isPending}
                    startIcon={
                      confirmarMutation.isPending
                        ? <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
                        : <Icon icon="lucide:check-check" />
                    }
                    sx={{ whiteSpace: "nowrap", textTransform: "none", fontWeight: 600 }}
                  >
                    Confirmar todo
                  </Button>
                </span>
              </Tooltip>
            )
          }
        >
          {sinConfirmar === 1 ? "1 cuenta la cargó" : `${sinConfirmar} cuentas las cargó`} el sistema con lo que hacía hasta hoy.
          Revisalas y confirmalas: la elección queda registrada a tu nombre.
        </Alert>
      )}

      {/* Comprobación visual del mapeo: cómo queda cada asiento automático. */}
      <AsientosEjemploPanel
        onIrAConcepto={irAConcepto}
        mapeoActual={currentMap}
        cuentas={flatCuentas}
        hayCambiosSinGuardar={dirty}
        etiquetasConcepto={etiquetasConcepto}
      />

      {/* La plata que entra y sale no siempre va a la misma cuenta: cada empresa
          decide qué cuenta corresponde a cada medio de pago. */}
      <MediosPagoMapeoCard cuentas={flatCuentas} puedeEditar={canConfig} />

      <Grid>
        {gruposVisibles.map(({ grupo, items, nota }) => (
          <GrupoCard key={grupo}>
            <GrupoHeader>
              <Icon icon="lucide:layers" />
              {grupo}
            </GrupoHeader>
            {nota && (
              <GrupoNota>
                <Icon icon="lucide:info" />
                <span>{nota}</span>
              </GrupoNota>
            )}
            {grupo === "Importaciones" && dobleIngresoImportacion && (
              <Alert severity="warning" icon={<Icon icon="lucide:copy" />} sx={{ borderRadius: 0, fontSize: 12 }}>
                {AVISO_DOBLE_INGRESO}
              </Alert>
            )}
            <GrupoBody>
              {items.map(({ key, label, icon, conModo }) => {
                const valor = currentMap[key] || null;
                const sumaAPrincipal = valor === SUMAR;
                const cuentaId = sumaAPrincipal ? null : valor;
                const cuenta = flatCuentas.find(c => c.id === cuentaId);
                // Apunta a una cuenta que ya no se puede elegir (dada de baja o
                // de agrupación): está mapeado, pero no sirve.
                const apuntaAInvalida = !!cuentaId && !cuenta;
                const cuentaInvalida = apuntaAInvalida ? cuentaDelMapeo.get(key) ?? null : null;
                const isMapped = sumaAPrincipal || (!!cuentaId && !apuntaAInvalida);
                const fila = mapeosPorConcepto.get(key);
                const estado = estadoDelConcepto(fila);
                // Sin fila (sin mapear) la recomendación sale del catálogo del backend.
                const recomendacion = fila?.recomendacion ?? recomendaciones[key] ?? null;
                const recomendacionEnUso = recomendacion
                  ? recomendacion.codigo
                    ? cuenta?.codigo === recomendacion.codigo
                    : !!conModo && sumaAPrincipal
                  : false;
                const recomendacionUsable = !!recomendacion && (!!recomendacion.codigo || !!conModo);
                // Vació la cuenta (o pasó de "Sumar" a "Una cuenta") sin elegir otra: no
                // hay nada para guardar y lo guardado sigue vigente.
                const faltaElegirCuenta = key in localMap && !localMap[key] && !!mapaGuardado[key];

                return (
                  <ConceptoRow key={key} id={`concepto-${key}`} $resaltado={key === conceptoResaltado}>
                    <ConceptoInfo>
                      <ConceptoIcon $mapped={isMapped}>
                        <Icon icon={icon} />
                      </ConceptoIcon>
                      <ConceptoText>
                        <ConceptoLabel>{label}</ConceptoLabel>
                        <ConceptoCodigo>{key}</ConceptoCodigo>
                      </ConceptoText>
                      {isMapped
                        ? <Tooltip title="Mapeado"><StatusDot $ok /></Tooltip>
                        : <Tooltip title={apuntaAInvalida ? "Apunta a una cuenta que ya no sirve" : "Sin mapear"}><StatusDot /></Tooltip>}
                      {apuntaAInvalida && (
                        <Tooltip
                          title={
                            cuentaInvalida
                              ? `Está apuntando a ${cuentaInvalida.codigo} — ${cuentaInvalida.descripcion}, que está dada de baja o pertenece al plan reemplazado (OLD-). Mientras siga así, el sistema no puede generar este asiento. Elegí una cuenta del plan vigente.`
                              : "Está apuntando a una cuenta que ya no existe o está dada de baja. Mientras siga así, el sistema no puede generar este asiento."
                          }
                        >
                          <Chip
                            size="small"
                            color="warning"
                            variant="outlined"
                            label={cuentaInvalida ? `apunta a ${cuentaInvalida.codigo}` : "cuenta de baja"}
                            icon={<Icon icon="lucide:alert-triangle" width={12} />}
                            sx={{ height: 20, fontSize: 10, fontWeight: 700 }}
                          />
                        </Tooltip>
                      )}
                    </ConceptoInfo>

                    <Selectores>
                    {conModo && (
                      <TextField
                        select
                        size="small"
                        label="Cómo se registra"
                        value={sumaAPrincipal ? "SUMAR_A_PRINCIPAL" : "CUENTA"}
                        onChange={(e) => handleModo(key, e.target.value)}
                        disabled={!canConfig}
                        sx={{ minWidth: 200, flex: sumaAPrincipal ? 1 : "0 0 200px", "& .MuiOutlinedInput-root": { borderRadius: "8px", fontSize: 13 } }}
                      >
                        <MenuItem value="CUENTA">Una cuenta</MenuItem>
                        <MenuItem value="SUMAR_A_PRINCIPAL">Sumar a la cuenta principal</MenuItem>
                      </TextField>
                    )}
                    {!sumaAPrincipal && (
                    <CuentaMapeoAutocomplete
                      cuentas={flatCuentas}
                      value={cuenta || null}
                      onChange={(val) => handleChange(key, val?.id || null)}
                      helperText={
                        faltaElegirCuenta
                          ? mapaGuardado[key] === SUMAR
                            ? "Elegí una cuenta para guardar. Mientras tanto se sigue sumando a la cuenta principal."
                            : "Elegí una cuenta para guardar. Mientras tanto sigue la cuenta guardada."
                          : undefined
                      }
                    />
                    )}
                    </Selectores>

                    <EstadoMapeo>
                      <Chip
                        size="small"
                        variant="outlined"
                        color={COLOR_ESTADO[estado.tono]}
                        label={estado.texto}
                        sx={{ height: 22, fontSize: 11, fontWeight: 600 }}
                      />
                      {recomendacion && (
                        <Recomendado>
                          <span>Recomendado: {recomendacion.texto}</span>
                          {canConfig && recomendacionUsable && (
                            <Button
                              size="small"
                              variant="text"
                              onClick={() => usarRecomendacion(key, recomendacion.codigo, conModo)}
                              disabled={recomendacionEnUso}
                              sx={{ minWidth: 0, py: 0, px: 1, fontSize: 12, textTransform: "none", fontWeight: 600 }}
                            >
                              {recomendacionEnUso ? "En uso" : "Usar"}
                            </Button>
                          )}
                        </Recomendado>
                      )}
                    </EstadoMapeo>

                    {/* Excepciones del concepto: otra cuenta según sucursal, moneda,
                        categoría, etc. Sin reglas, el concepto usa la cuenta de arriba. */}
                    <ReglasConceptoPanel
                      concepto={key}
                      etiqueta={label}
                      conModo={!!conModo}
                      cuentas={flatCuentas}
                      canConfig={canConfig}
                    />
                  </ConceptoRow>
                );
              })}
            </GrupoBody>
          </GrupoCard>
        ))}
      </Grid>

      {dirty && canConfig && (
        <FloatSave>
          <Icon icon="lucide:circle-alert" />
          Tenés cambios sin guardar
          <SaveBtn $small onClick={handleGuardar} disabled={actualizarMutation.isPending}>
            {actualizarMutation.isPending
              ? <Icon icon="lucide:loader-circle" style={{ animation: "spin 1s linear infinite" }} />
              : <Icon icon="lucide:save" />}
            Guardar
          </SaveBtn>
        </FloatSave>
      )}

      <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
    </Wrapper>
  );
}

// ── Styles ────────────────────────────────────────────────────────────────────
const Wrapper = styled.div`position: relative;`;

const TopBar = styled.div`
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 24px; flex-wrap: wrap;
`;
const TitleBlock = styled.div`
  display: flex; align-items: flex-start; gap: 12px;
  > svg { width: 24px; height: 24px; color: #f88533; flex-shrink: 0; margin-top: 2px; }
  h2 { margin: 0 0 4px; font-size: 16px; font-weight: 700; color: ${({ theme }) => theme.text}; }
  p  { margin: 0; font-size: 13px; color: ${({ theme }) => theme.text}88; max-width: 500px; }
`;
const ActionsRow = styled.div`
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
`;
const SeedBtn = styled.button`
  display: flex; align-items: center; gap: 7px;
  padding: 9px 16px;
  background: transparent; color: #f88533;
  border: 1.5px solid #f88533; border-radius: 8px;
  font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: all 0.15s;
  &:hover:not(:disabled) { background: #f8853311; }
  &:disabled { opacity: 0.5; cursor: not-allowed; }
  svg { width: 15px; height: 15px; }
`;
const SaveBtn = styled.button`
  display: flex; align-items: center; gap: 7px;
  padding: ${({ $small }) => $small ? "6px 14px" : "9px 20px"};
  background: #f88533; color: #fff; border: none; border-radius: 8px;
  font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: background 0.15s;
  &:hover:not(:disabled) { background: #e0742a; }
  &:disabled { opacity: 0.6; cursor: not-allowed; }
  svg { width: 15px; height: 15px; }
`;

const Grid = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(480px, 1fr));
  gap: 16px;
  @media (max-width: 768px) { grid-template-columns: 1fr; }
`;

const GrupoCard = styled.div`
  background: ${({ theme }) => theme.bg2};
  border: 1px solid ${({ theme }) => theme.color2};
  border-radius: 12px; overflow: hidden;
  /* Las filas se acomodan por el ancho de la tarjeta, no por el de la pantalla. */
  container-type: inline-size;
`;
const GrupoHeader = styled.div`
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px; font-size: 13px; font-weight: 700;
  background: ${({ theme }) => theme.bg3};
  color: ${({ theme }) => theme.text};
  border-bottom: 1px solid ${({ theme }) => theme.color2};
  svg { width: 16px; height: 16px; color: #f88533; }
`;
const GrupoNota = styled.p`
  display: flex;
  gap: 8px;
  margin: 0;
  padding: 10px 14px;
  border-bottom: 1px solid ${({ theme }) => theme.bg4}60;
  background: ${({ theme }) => theme.bg3}35;
  font-size: 0.75rem;
  line-height: 1.5;
  color: ${({ theme }) => theme.text}b0;

  svg {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
    margin-top: 2px;
    opacity: 0.8;
  }
`;

const GrupoBody = styled.div`display: flex; flex-direction: column; gap: 0;`;

const ConceptoRow = styled.div`
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid ${({ theme }) => theme.color2};
  transition: background 0.3s ease, box-shadow 0.3s ease;
  ${({ $resaltado }) => $resaltado && `
    background: #f8853318;
    box-shadow: inset 3px 0 0 #f88533;
  `}
  &:last-child { border-bottom: none; }
  @container (max-width: 430px) { flex-direction: column; align-items: stretch; }
`;
const ConceptoInfo = styled.div`
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  /* Crece con la etiqueta y con la advertencia, sin pisar al selector. */
  flex: 1 1 220px; min-width: 0;
`;
const ConceptoIcon = styled.div`
  width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: ${({ $mapped }) => $mapped ? "#f88533" : "#e2e8f0"};
  svg { width: 15px; height: 15px; color: ${({ $mapped }) => $mapped ? "#fff" : "#94a3b8"}; }
`;
const ConceptoText = styled.div`flex: 1 1 160px; min-width: 0;`;
const ConceptoLabel = styled.div`font-size: 13px; font-weight: 600; color: ${({ theme }) => theme.text};`;
const ConceptoCodigo = styled.div`font-size: 11px; color: ${({ theme }) => theme.text}66; font-family: monospace;`;
const StatusDot = styled.div`
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  background: ${({ $ok }) => $ok ? "#22c55e" : "#e2e8f0"};
  border: 1.5px solid ${({ $ok }) => $ok ? "#16a34a" : "#cbd5e1"};
`;

/* Modo (si el concepto lo admite) + cuenta: comparten fila y se apilan si la tarjeta es angosta. */
const Selectores = styled.div`
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  flex: 1 1 260px; min-width: 0;
`;
/* Estado de lo guardado y recomendación: ocupa todo el ancho, debajo del selector. */
const EstadoMapeo = styled.div`
  flex: 1 1 100%;
  display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap;
  padding-left: 40px;
  @container (max-width: 430px) { padding-left: 0; }
`;
const Recomendado = styled.div`
  display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap;
  font-size: 12px; color: ${({ theme }) => theme.textSecondary};
`;

const FloatSave = styled.div`
  position: sticky; bottom: 16px; margin-top: 24px;
  display: flex; align-items: center; gap: 12px;
  background: #1e293b; color: #fff; border-radius: 10px;
  padding: 12px 16px; font-size: 13px; font-weight: 500;
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
  svg { width: 16px; height: 16px; color: #fbbf24; }
`;

const LoadingWrap = styled.div`
  display: flex; align-items: center; justify-content: center;
  gap: 10px; padding: 60px; color: ${({ theme }) => theme.text}88; font-size: 14px;
  svg { width: 20px; height: 20px; }
`;
const EmptyState = styled.div`
  display: flex; flex-direction: column; align-items: center;
  gap: 8px; padding: 60px 20px; text-align: center;
  color: ${({ theme }) => theme.text}88;
  svg { width: 40px; height: 40px; opacity: 0.4; }
  strong { font-size: 15px; color: ${({ theme }) => theme.text}; }
  p { font-size: 13px; margin: 0; }
`;
