import { PRICES, PUMP_TRUCK_FEES, CONSTRAINTS, COVERAGE_AREAS, FLOORING_TYPES, type FlooringType, } from "./constants" export interface CalculationInput { area: number // m² height: number // cm postalCode: string distance: number // km (round trip) // Optional components includeInsulation?: boolean // default: true includeFloorHeating?: boolean // default: true includeCompound?: boolean // default: true flooringType?: FlooringType // default: KLINKER } export interface CalculationDetails { // Input values area: number height: number postalCode: string distance: number // Optional component selections includeInsulation: boolean includeFloorHeating: boolean includeCompound: boolean flooringType: FlooringType // Calculated values insulationThickness: number // cm insulationVolume: number // m³ compoundWeight: number // kg // Component prices insulation: number floorHeating: number syntheticNet: number selfLevelingCompound: number pumpTruckFee: number startFee: number // Subtotals subtotal: number coveringFee: number wasteFee: number totalFees: number // Transport transport: number bridgeFee: number // Totals totalExclVat: number vat: number totalInclVat: number } export function calculateInsulation( area: number, height: number ): { thickness: number volume: number volumePrice: number baseLabor: number price: number } { const thickness = Math.max(0, height - CONSTRAINTS.CONCRETE_THICKNESS) const volume = area * (thickness / 100) // Volume-based cost (materials + labor per m³) const volumePrice = thickness > 0 ? volume * PRICES.INSULATION_TOTAL_PER_M3 : 0 // Base labor cost (always applied per m² when insulation is included) const baseLabor = area * PRICES.INSULATION_BASE_LABOR // Total insulation price const price = volumePrice + baseLabor return { thickness, volume, volumePrice, baseLabor, price } } export function calculatePumpTruckFee(weight: number): number { const tier = PUMP_TRUCK_FEES.find((tier) => weight > tier.minWeight) return tier?.fee ?? PUMP_TRUCK_FEES[PUMP_TRUCK_FEES.length - 1].fee } export function getBridgeFee(postalCode: string): number { const postalNumber = parseInt(postalCode) for (const area of Object.values(COVERAGE_AREAS)) { if (postalNumber >= area.start && postalNumber <= area.end) { return area.bridgeFee } } return 0 } export function calculatePrice(input: CalculationInput): CalculationDetails { const { area, height, postalCode, distance, includeInsulation = true, includeFloorHeating = true, includeCompound = true, flooringType = "STANDARD", } = input // Step 1: Calculate derived values const insulation = calculateInsulation(area, height) const compoundWeight = includeCompound ? area * PRICES.COMPOUND_WEIGHT_PER_M2 : 0 // Get flooring type multiplier const flooringConfig = FLOORING_TYPES[flooringType] const compoundMultiplier = flooringConfig?.compoundMultiplier ?? 1.0 // Step 2: Calculate components (only if included) const insulationPrice = includeInsulation ? insulation.price : 0 const floorHeating = includeFloorHeating ? area * PRICES.FLOOR_HEATING_TOTAL : 0 const syntheticNet = includeFloorHeating ? area * PRICES.SYNTHETIC_NET_TOTAL : 0 // Net only with heating const selfLevelingCompound = includeCompound ? area * PRICES.SELF_LEVELING_COMPOUND * compoundMultiplier : 0 const pumpTruckFee = includeCompound ? calculatePumpTruckFee(compoundWeight) : 0 const startFee = PRICES.START_FEE // Step 3: Calculate subtotal const subtotal = insulationPrice + floorHeating + syntheticNet + selfLevelingCompound + pumpTruckFee + startFee // Step 4: Calculate percentage fees const coveringFee = subtotal * PRICES.COVERING_PERCENTAGE const wasteFee = subtotal * PRICES.WASTE_PERCENTAGE const totalFees = coveringFee + wasteFee // Step 5: Calculate transport const transport = distance * PRICES.TRANSPORT_PER_KM const bridgeFee = getBridgeFee(postalCode) // Step 6: Calculate totals const totalExclVat = subtotal + totalFees + transport + bridgeFee const vat = totalExclVat * PRICES.VAT const totalInclVat = totalExclVat * (1 + PRICES.VAT) return { // Input values area, height, postalCode, distance, // Optional component selections includeInsulation, includeFloorHeating, includeCompound, flooringType, // Calculated values insulationThickness: insulation.thickness, insulationVolume: insulation.volume, compoundWeight, // Component prices insulation: insulationPrice, floorHeating, syntheticNet, selfLevelingCompound, pumpTruckFee, startFee, // Subtotals subtotal, coveringFee, wasteFee, totalFees, // Transport transport, bridgeFee, // Totals totalExclVat, vat, totalInclVat, } } export function formatPrice(price: number): string { return new Intl.NumberFormat("da-DK", { style: "currency", currency: "DKK", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(price) } export function formatEstimate(price: number): string { // Round to nearest 500 const rounded = Math.round(price / 500) * 500 return `Ca. ${formatPrice(rounded)}` }