// Reuse Icon component pattern from website kit (kept self-contained here)
function Icon({ name, size = 18, stroke = 'currentColor', sw = 1.5 }) {
  const p = {
    shield:<path d="M12 2L3 7v6c0 5 4 9 9 10 5-1 9-5 9-10V7l-9-5z"/>,
    monitor:<g><rect x="3" y="4" width="18" height="12" rx="1"/><path d="M2 20h20"/></g>,
    phone:<path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72c.13.96.37 1.9.72 2.81a2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0122 16.92z"/>,
    document:<g><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></g>,
    arrow:<path d="M5 12h14M13 6l6 6-6 6"/>,
    chevron:<path d="M9 18l6-6-6-6"/>,
    check:<path d="M20 6L9 17l-5-5"/>,
    plus:<path d="M12 5v14M5 12h14"/>,
    close:<path d="M18 6L6 18M6 6l12 12"/>,
    search:<g><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></g>,
    mail:<g><path d="M4 4h16v16H4z"/><path d="M22 6l-10 7L2 6"/></g>,
    download:<g><path d="M12 3v14M5 12l7 7 7-7M3 21h18"/></g>,
    print:<g><path d="M6 9V3h12v6M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/><path d="M6 14h12v8H6z"/></g>,
    trash:<g><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></g>,
    edit:<g><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></g>,
    file:<g><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></g>,
    send:<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>,
    eye:<g><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></g>,
    user:<g><circle cx="12" cy="8" r="4"/><path d="M4 22c0-4 4-7 8-7s8 3 8 7"/></g>,
    duplicate:<g><rect x="9" y="9" width="12" height="12" rx="1"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></g>,
    server:<g><rect x="3" y="4" width="18" height="6" rx="1"/><rect x="3" y="14" width="18" height="6" rx="1"/><path d="M7 7h.01M7 17h.01"/></g>,
    network:<g><circle cx="12" cy="5" r="2"/><circle cx="5" cy="19" r="2"/><circle cx="19" cy="19" r="2"/><path d="M12 7v5M12 12L5 17M12 12l7 5"/></g>,
    video:<g><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M22 8l-6 4 6 4V8z"/></g>,
    wallet:<g><path d="M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z"/><path d="M16 12h4M3 9h18"/></g>,
    wrench:<path d="M14 7a5 5 0 016.32 6.32L21 14l-7 7-3-3 7-7 .68-.68A5 5 0 0014 7zM7 17l-4 4"/>,
    layout:<g><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></g>,
    lock:<g><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V8a4 4 0 118 0v3"/></g>,
    logout:<g><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><path d="M16 17l5-5-5-5M21 12H9"/></g>,
  };
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
    stroke={stroke} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round">{p[name]}</svg>;
}
window.Icon = Icon;

// Number helpers
const fmtKr = n => (Math.round(n || 0)).toLocaleString('sv-SE').replace(/,/g, ' ') + ' kr';
const fmtNum = n => (n || 0).toLocaleString('sv-SE').replace(/,/g, ' ');

// Radsumma på 0 kr (t.ex. en tillbehörsrad som ingår i huvudartikelns pris,
// vanligt på hårdvarupaket/skrivarlösningar) läses lättare som "Ingår" än "0 kr".
// MEN: ett TOMT prisfält är inte samma sak som ett medvetet nollpris. Tidigare
// blev ett rensat pris 0, och kunden fick läsa "Ingår" — alltså att produkten
// var kostnadsfri. Tomt värde (null) visas därför som "—" istället.
const fmtKrOrIngar = n => (n == null ? '—' : (n === 0 ? 'Ingår' : fmtKr(n)));

// Tolkar ett inskrivet tal. Svenskar skriver "1234,50" och ibland "1 234,50";
// ett vanligt number-fält ger då tomt värde, så beloppet blev tyst 0.
// Tomt fält ger null (= "inte ifyllt"), inte 0.
const parseNum = (v) => {
  if (v == null) return null;
  const s = String(v).replace(/\s/g, '').replace(',', '.');
  if (s === '') return null;
  const n = Number(s);
  return Number.isFinite(n) ? n : null;
};
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);

// Gemensam prisuträkning för BÅDE redigeraren och kunddokumentet, så de aldrig
// kan visa olika siffror. Avrundningen sker en gång och används genomgående —
// annars kan kunden se rader som inte summerar ihop (1 667 + 417 ≠ 2 083).
function calcOffer(offer) {
  const lines = offer.lines || [];
  const services = offer.services || [];
  const lage = offer.pricingMode || 'kontant';
  const sub = Math.round(lines.reduce((s, l) => s + num(l.qty) * num(l.price), 0));
  // Rabatten gäller bara köppriset. DLL:s månadspris är redan ett nettopris.
  const disc = lage === 'hyra' ? 0 : Math.round(sub * num(offer.discount) / 100);
  const net = sub - disc;
  const vat = Math.round(net * 0.25);
  const total = net + vat;
  const monthlyServiceTotal = Math.round(services.reduce((s, l) => s + num(l.qty) * num(l.price), 0));

  // Finansierat: varje rad har sitt eget månadspris (kr/mån per styck) som
  // skrivs in från finansiärens (DLL:s) offert. Hyran per månad är summan av
  // dem. Tidigare räknades i stället köpsumman delat på avtalstiden, vilket
  // gav ett påhittat belopp som inte stämde med finansiärens — och köppriserna
  // stod kvar i dokumentet.
  const hyraPerManad = Math.round(lines.reduce((s, l) => s + num(l.qty) * num(l.monthly), 0));
  const hyraSaknas = lage === 'kontant' ? [] : lines.filter(l => l.monthly == null || l.monthly === '');
  const termMissing = !(num(offer.term) > 0);
  const term = termMissing ? 48 : num(offer.term);
  const monthly = hyraPerManad;
  // Totalt per månad i finansierat läge: hyran plus ULAB:s egna månadstjänster.
  const totaltPerManad = hyraPerManad + monthlyServiceTotal;

  return { sub, disc, net, vat, total, monthlyServiceTotal, term, termMissing, monthly, hyraPerManad, hyraSaknas, totaltPerManad, lage,
           // Behålls för äldre kod som läser dem — restvärde används inte längre.
           residualPct: 0, computedMonthly: hyraPerManad };
}

window.fmtKr = fmtKr;
window.fmtNum = fmtNum;
window.fmtKrOrIngar = fmtKrOrIngar;
window.parseNum = parseNum;
window.calcOffer = calcOffer;
