import { deriveCantidades } from './derive';
import { resolvePrecioUnitario } from './resolve';
import { PARTIDA_LABEL, PARTIDA_ORDER } from './labels';
import type {
  BudgetInputs,
  BudgetResult,
  CategoriaMaterial,
  CatalogItem,
  PartidaKey,
  PricingConfig,
  TipoReforma,
} from './types';

const LICENCIA_MIN = 30000; // 300 €
const LICENCIA_MAX = 150000; // 1.500 €

// Zonas húmedas: las únicas que llevan impermeabilización.
const WET = new Set<TipoReforma>(['cocina', 'bano', 'integral']);

// Intensidad de instalaciones (fontanería/electricidad) por m² según el tipo de reforma.
// Baseline cocina = 1.0. Un baño concentra más instalaciones por m²; un salón o un piso
// integral las diluye. Corrige el sesgo del modelo lineal €/m² sin rehacerlo.
const TIPO_INTENSIDAD: Record<TipoReforma, number> = {
  cocina: 1.0,
  bano: 1.3,
  integral: 0.45,
  salon: 0.4,
  comedor: 0.4,
  otro: 0.7,
};

// A qué partida contribuye el material de cada categoría.
const MATERIAL_PARTIDA: Record<CategoriaMaterial, PartidaKey> = {
  suelo: 'alicatado',
  pared: 'alicatado',
  pintura: 'extras',
  mobiliario: 'carpinteria',
};

const CATEGORIAS: CategoriaMaterial[] = ['suelo', 'pared', 'pintura', 'mobiliario'];

export function computeBudget(
  inputs: BudgetInputs,
  config: PricingConfig,
  catalog: CatalogItem[],
): BudgetResult {
  const cant = deriveCantidades(inputs, config);
  const avisos: string[] = [];
  const materialesRender: string[] = [];

  const importes: Record<PartidaKey, number> = {
    demolicion: 0,
    impermeabilizacion: 0,
    alicatado: 0,
    fontaneria: 0,
    electricidad: 0,
    carpinteria: 0,
    mano_de_obra: 0,
    extras: 0,
    extras_fijos: 0,
    licencia: 0,
  };

  const cantidadPorCategoria: Record<CategoriaMaterial, number> = {
    suelo: cant.m2Suelo,
    pared: cant.m2Pared,
    pintura: cant.m2Pared,
    mobiliario: cant.mlMobiliario,
  };

  for (const categoria of CATEGORIAS) {
    const cantidad = cantidadPorCategoria[categoria];
    if (cantidad <= 0) continue;
    const { item } = resolvePrecioUnitario(
      categoria,
      inputs.calidadGlobal,
      catalog,
      inputs.materialSelections,
    );
    if (!item) {
      avisos.push(`Sin precio para ${categoria} (calidad ${inputs.calidadGlobal})`);
      continue;
    }
    importes[MATERIAL_PARTIDA[categoria]] += Math.round(cantidad * item.precioUnit);
    if (item.descriptorRender) materialesRender.push(item.descriptorRender);
  }

  const intensidad = TIPO_INTENSIDAD[inputs.tipoReforma] ?? 1;
  importes.demolicion += Math.round(cant.m2Suelo * config.manoObra.demolicion);
  importes.fontaneria += Math.round(cant.m2Suelo * config.manoObra.fontaneria * intensidad);
  importes.electricidad += Math.round(cant.m2Suelo * config.manoObra.electricidad * intensidad);
  importes.mano_de_obra += Math.round(cant.m2Suelo * config.manoObra.mano_de_obra);

  // Impermeabilización: solo en zonas húmedas, proporcional al suelo a tratar.
  if (WET.has(inputs.tipoReforma)) {
    importes.impermeabilizacion += Math.round(cant.m2Suelo * config.manoObra.impermeabilizacion);
  }

  // Extras fijos (no escalan con m²). El boletín eléctrico es siempre obligatorio.
  const extras = config.extras;
  if (extras) {
    importes.extras_fijos += extras.boletin;
    if (inputs.anteriorA2000) importes.extras_fijos += extras.tuberias;
    if (inputs.cambioDistribucion) importes.extras_fijos += extras.distribucion;
  }

  if (inputs.estructural) importes.licencia += LICENCIA_MIN;

  const partidas = PARTIDA_ORDER.filter((k) => importes[k] > 0).map((k) => ({
    key: k,
    label: PARTIDA_LABEL[k],
    importe: importes[k],
  }));

  const subtotal = partidas.reduce((s, p) => s + p.importe, 0);
  const factorZona = config.factorZona[inputs.provincia ?? ''] ?? 1;
  const total = Math.round(subtotal * factorZona);

  const hasExact = (Object.values(inputs.materialSelections) as string[]).some(
    (id) => catalog.some((c) => c.id === id),
  );
  const hasM2 = inputs.m2Suelo != null && inputs.m2Suelo > 0;
  let confianza: BudgetResult['confianza'];
  let band: number;
  if (hasM2 && hasExact) {
    confianza = 'alta';
    band = 0.1;
  } else if (hasM2 || hasExact) {
    confianza = 'media';
    band = 0.15;
  } else {
    confianza = 'baja';
    band = 0.25;
  }

  const rango = {
    min: Math.round(total * (1 - band)),
    max: Math.round(total * (1 + band)) + (inputs.estructural ? LICENCIA_MAX - LICENCIA_MIN : 0),
  };

  return { partidas, subtotal, factorZona, total, rango, confianza, materialesRender, avisos };
}
