// Kunddokumentet — sidbryts efter FAKTISKT uppmätt höjd.
//
// Tidigare gissades höjden på varje avsnitt ur textens längd
// (Math.ceil(text.length / 70) * 24). Gissningen tog ingen hänsyn till
// radbrytning på ord, långa svenska sammansättningar, punktlistor eller
// rubriker — och eftersom en sida har fast höjd med overflow:hidden
// FÖRSVANN texten som inte fick plats, tyst, i dokumentet som gick till kund.
//
// Nu renderas allt innehåll först en gång i en osynlig mätyta med exakt samma
// bredd och stilar som en riktig sida. Varje litet block mäts i webbläsaren och
// sidorna packas sedan med riktiga siffror. Ingenting kan klippas bort, och
// blir ett avsnitt för långt fortsätter det på nästa sida istället.

// Sidans mått (A4 vid 96 dpi) och hur mycket av den som är innehållsyta.
const SIDA_BREDD = 794, SIDA_HOJD = 1123;
const SIDA_PAD_X = 64, SIDA_PAD_TOPP = 48, SIDA_PAD_BOTTEN = 56;
const INNEHALL_BREDD = SIDA_BREDD - SIDA_PAD_X * 2;      // 666 px
const SIDHUVUD_HOJD = 55;                                 // kund + sidnummer
const SIDFOT_HOJD = 46;                                   // adressraden längst ner
// Marginal på 12 px extra så en halv textrad aldrig hamnar under sidfoten.
const INNEHALL_HOJD = SIDA_HOJD - SIDA_PAD_TOPP - SIDA_PAD_BOTTEN - SIDHUVUD_HOJD - SIDFOT_HOJD - 12;
const AVSNITT_MELLANRUM = 56;

function OfferPreview({ offer, productSheetItems }) {
  const sheetItems = productSheetItems || [];
  // Exakt samma uträkning som redigeraren använder (calcOffer i Icon.jsx).
  const { sub, disc, net, vat, total, term, monthlyServiceTotal, hyraPerManad, totaltPerManad } = calcOffer(offer);
  const mode = offer.pricingMode || 'kontant';
  const services = offer.services || [];
  const lines = offer.lines || [];

  const groups = lines.reduce((g, l) => { (g[l.cat || 'Övrigt'] ||= []).push(l); return g; }, {});

  const ORDER = ['summary','need','solution','products','services','cost','finance','support','about','terms','close'];
  const orderKey = (s) => {
    if (s.isProductList) return ORDER.indexOf('products');
    if (s.isServiceList) return ORDER.indexOf('services');
    if (s.kind === 'cost') return ORDER.indexOf('cost');
    const idx = ORDER.indexOf(s.kind);
    // Egna avsnitt (t.ex. "Månadslicenser") hamnade tidigare EFTER Avslutning
    // oavsett var säljaren placerat dem. Nu ärver de ordningen från avsnittet
    // före dem i redigeraren och stannar där de lades.
    return idx >= 0 ? idx : null;
  };
  let inherited = 0;
  const keyed = [...(offer.sections || [])].map((s, i) => {
    const k = orderKey(s);
    if (k == null) return { s, i, k: inherited + 0.5 };
    inherited = k;
    return { s, i, k };
  });
  const rawSections = keyed.sort((a, b) => (a.k !== b.k ? a.k - b.k : a.i - b.i)).map(x => x.s);

  // Kostnadssammanställningen ska alltid med — annars hamnar pris och totaler
  // aldrig i dokumentet.
  const hasCost = rawSections.some(s => s.kind === 'cost');
  const sections = hasCost ? rawSections : (() => {
    const arr = [...rawSections];
    const servicesIdx = arr.findIndex(s => s.isServiceList);
    const productsIdx = arr.findIndex(s => s.isProductList);
    const anchorIdx = servicesIdx >= 0 ? servicesIdx : productsIdx;
    arr.splice(anchorIdx >= 0 ? anchorIdx + 1 : arr.length, 0, {
      id:'auto-cost', kind:'cost', title:'Kostnadssammanställning', body:'', isProductList:false, isServiceList:false, _auto:true,
    });
    return arr;
  })();

  // Tomma textavsnitt döljs — en rubrik utan text under ser oavslutad ut.
  const visibleSections = sections.filter(s => (
    s.isProductList || s.isServiceList || s.kind === 'cost' || (s.body && s.body.trim())
  ));

  // ---------- Bygg block ----------
  // Ett block är en sak som ritas: en rubrik, ett stycke, en tabellrad, en
  // summaruta. Blocken samlas sedan i STYCKEN (chunks) — det minsta som får
  // flyttas till nästa sida. Det är i styckena reglerna för sidbrytning bor:
  //
  //  • En avsnittsrubrik står aldrig ensam med bara ett block under sig. Den
  //    limmas ihop med minst två block, eller hela avsnittet om det är kortare.
  //    (Tidigare räckte ett enda block, så rubriken "Lösning" kunde stå längst
  //    ned på en sida med en rad text, och resten av avsnittet på nästa.)
  //  • En tabell med högst 6 rader delas aldrig — den flyttas hel, med sin
  //    summa. (Tidigare kunde en tabell med två rader klyvas så att en rad och
  //    summan stod ensamma överst på nästa sida.)
  //  • En längre tabell får delas, men aldrig så att färre än två rader blir
  //    kvar på någon sida, och summan följer alltid med minst två rader.
  //  • Börjar en sida mitt i ett avsnitt får den en "(forts.)"-rubrik, och
  //    mitt i en tabell även tabellens kolumnrubriker.
  const blocks = [];
  const chunks = [];
  const fortsMatt = [];           // block som bara mäts: forts.-rubriker och kolumnhuvuden
  const nyttBlock = (b) => { blocks.push(b); return b; };

  const perManad = services.length > 0 && services.every(l => opKortEnhet(l.unit) === 'mån');

  // Delar en tabell i stycken enligt reglerna ovan. huvud = kolumnhuvudet,
  // fore = block som ska limmas FÖRE (avsnittsrubriken), efter = block som ska
  // limmas EFTER sista raden (summan).
  const tabellStycken = (fore, huvud, rader, efter) => {
    const MAX_HEL = 6;
    if (rader.length <= MAX_HEL) { chunks.push([...fore, huvud, ...rader, ...efter]); return; }
    chunks.push([...fore, huvud, rader[0], rader[1]]);
    for (let i = 2; i < rader.length - 2; i++) chunks.push([rader[i]]);
    chunks.push([rader[rader.length - 2], rader[rader.length - 1], ...efter]);
  };

  visibleSections.forEach((s, si) => {
    const sid = String(s.id);
    const forstaIndex = blocks.length;
    const titel = nyttBlock({
      id: `${sid}:titel`, sektion: sid, titel: s.title, roll: 'titel',
      node: <SectionTitle title={s.title} />,
    });
    fortsMatt.push({ id: `${sid}:forts`, node: <SectionTitle title={`${s.title} (forts.)`} /> });

    if (s.isProductList) {
      const kategorier = Object.entries(groups);
      if (!lines.length) {
        // Inga rader: bara en rad text — ingen summaruta på 0 kr före den.
        chunks.push([titel, nyttBlock({ id: `${sid}:tom`, sektion: sid, titel: s.title, roll: 'tom', node: <Tom text="Inga produktrader angivna." /> })]);
      } else {
        // Summan hör till sista kategorins tabell och följer med dess sista rader.
        const summa = {
          id: `${sid}:summa`, sektion: sid, titel: s.title, roll: 'summa',
          node: <ProductSummary offer={offer} sub={sub} disc={disc} net={net} mode={mode} hyraPerManad={hyraPerManad} />,
        };
        kategorier.forEach(([cat, catLines], ki) => {
          const tabell = `${sid}:cat:${cat}`;
          const huvud = nyttBlock({ id: `${tabell}:huvud`, sektion: sid, titel: s.title, roll: 'kolhuvud', tabell,
            node: <OpProduktHuvud cat={cat} mode={mode} /> });
          fortsMatt.push({ id: `${tabell}:forts`, node: <OpProduktHuvud cat={cat} mode={mode} forts /> });
          const rader = catLines.map(l => nyttBlock({
            id: `${tabell}:rad:${l.id}`, sektion: sid, titel: s.title, roll: 'rad', tabell, cat,
            node: <ProductRow l={l} mode={mode} />,
          }));
          const sista = ki === kategorier.length - 1;
          if (sista) { summa.tabell = tabell; nyttBlock(summa); }
          tabellStycken(ki === 0 ? [titel] : [], huvud, rader, sista ? [summa] : []);
        });
      }
    } else if (s.isServiceList) {
      if (!services.length) {
        chunks.push([titel, nyttBlock({ id: `${sid}:tom`, sektion: sid, titel: s.title, roll: 'tom', node: <Tom text="Inga tjänster angivna." /> })]);
      } else {
        const tabell = `${sid}:tjanster`;
        const huvud = nyttBlock({ id: `${tabell}:huvud`, sektion: sid, titel: s.title, roll: 'kolhuvud', tabell,
          node: <OpTjanstHuvud perManad={perManad} /> });
        fortsMatt.push({ id: `${tabell}:forts`, node: <OpTjanstHuvud perManad={perManad} /> });
        const rader = services.map(l => nyttBlock({
          id: `${tabell}:rad:${l.id}`, sektion: sid, titel: s.title, roll: 'rad', tabell,
          node: <ServiceRow l={l} />,
        }));
        const summa = nyttBlock({ id: `${tabell}:summa`, sektion: sid, titel: s.title, roll: 'summa', tabell,
          node: <ServiceSummary services={services} perManad={perManad} /> });
        tabellStycken([titel], huvud, rader, [summa]);
      }
    } else if (s.kind === 'cost') {
      chunks.push([titel, nyttBlock({
        id: `${sid}:kostnad`, sektion: sid, titel: s.title, roll: 'kostnad',
        node: <CostSummary offer={offer} sub={sub} disc={disc} net={net} vat={vat} total={total}
          mode={mode} term={term} hyraPerManad={hyraPerManad} totaltPerManad={totaltPerManad}
          services={services} monthlyServiceTotal={monthlyServiceTotal} body={s.body} />,
      })]);
    } else {
      // Textavsnitt. Texten kan ha rubriker (##), underrubriker (###),
      // punktlistor (-) och fet stil (**) — se KlistraIn.jsx. Reglerna:
      //  • en rubrik i texten limmas alltid med det som kommer under den
      //  • en punktlista med högst 6 punkter delas aldrig; en längre delas med
      //    minst två punkter på varje sida
      //  • avsnittets rubrik limmas med de två första delarna (eller allt, om
      //    avsnittet har högst två)
      const delar = kiTolka(s.body);
      const inre = [];
      let vantar = [];   // rubriker som väntar på innehållet under sig
      delar.forEach((d, i) => {
        const bas = { sektion: sid, titel: s.title };
        if (d.typ === 'rubrik' || d.typ === 'underrubrik') {
          vantar.push(nyttBlock({ ...bas, id: `${sid}:p${i}`, roll: 'rubrik',
            node: d.typ === 'rubrik' ? <OpRubrik text={d.text} /> : <OpUnderrubrik text={d.text} /> }));
          return;
        }
        let grupper;
        if (d.typ === 'stycke') {
          grupper = [[nyttBlock({ ...bas, id: `${sid}:p${i}`, roll: 'text', node: <TextBlock text={d.text} /> })]];
        } else {
          const punkter = d.punkter.map((t, j) => nyttBlock({ ...bas, id: `${sid}:p${i}:${j}`, roll: 'punkt',
            node: <OpPunkt text={t} sist={j === d.punkter.length - 1} /> }));
          grupper = punkter.length <= 6 ? [punkter]
            : [punkter.slice(0, 2), ...punkter.slice(2, -2).map(p => [p]), punkter.slice(-2)];
        }
        grupper[0] = [...vantar, ...grupper[0]];
        vantar = [];
        inre.push(...grupper);
      });
      if (vantar.length) { if (inre.length) inre[inre.length - 1].push(...vantar); else inre.push(vantar); }
      if (inre.length <= 2) chunks.push([titel, ...inre.flat()]);
      else {
        chunks.push([titel, ...inre[0], ...inre[1]]);
        inre.slice(2).forEach(c => chunks.push(c));
      }
    }

    // Luft efter avsnittet (utom det sista). Räknas bara när något följer
    // efter på samma sida — se packningen nedan.
    const sista = blocks[blocks.length - 1];
    if (sista && blocks.length > forstaIndex && si < visibleSections.length - 1) sista.gapEfter = AVSNITT_MELLANRUM;
  });

  // ---------- Mät ----------
  const [matt, setMatt] = React.useState({});
  const matRef = React.useRef(null);
  const nyckel = blocks.map(b => b.id).join('|') + '#' + JSON.stringify(offer.sections || []).length + '#' + lines.length + '#' + services.length;

  React.useLayoutEffect(() => {
    if (!matRef.current) return;
    const nya = {};
    for (const el of matRef.current.querySelectorAll('[data-mid]')) {
      // getBoundingClientRect ger decimaler — avrunda uppåt, hellre en sida för
      // mycket än en klippt rad.
      nya[el.getAttribute('data-mid')] = Math.ceil(el.getBoundingClientRect().height);
    }
    setMatt(prev => {
      const lika = Object.keys(nya).length === Object.keys(prev).length &&
        Object.keys(nya).every(k => prev[k] === nya[k]);
      return lika ? prev : nya;
    });
  }, [nyckel, offer.discount, offer.pricingMode, offer.term, offer.financier]);

  // Innan mätningen hunnit köra används en grov uppskattning, bara så första
  // renderingen inte blir tokig. Den används aldrig för den färdiga utskriften.
  const hojd = (b) => (matt[b.id] != null ? matt[b.id] : 60);
  const allaMatta = blocks.every(b => matt[b.id] != null);

  // ---------- Packa sidor ----------
  // Ett stycke som börjar mitt i ett avsnitt får en forts.-rubrik (och mitt i
  // en tabell även kolumnhuvudet) om det hamnar först på en sida. Den höjden
  // måste räknas in, annars kan sidan bli för full.
  const mittI = (c) => c[0].roll !== 'titel';
  const iTabell = (c) => c[0].roll === 'rad' || c[0].roll === 'summa';
  const fortsHojd = (c) => {
    if (!mittI(c)) return 0;
    const t = matt[`${c[0].sektion}:forts`] ?? 60;
    const k = iTabell(c) && c[0].tabell ? (matt[`${c[0].tabell}:forts`] ?? 30) : 0;
    return t + k;
  };
  // Höjden ett stycke tar UTAN luften efter sig — luften behövs bara om något
  // kommer efter. (Tidigare räknades den alltid, och ett avsnitt kunde skickas
  // till nästa sida trots att det fick plats.)
  const styckeHojd = (c) => c.reduce((h, b, i) => h + hojd(b) + (i < c.length - 1 ? (b.gapEfter || 0) : 0), 0);
  const styckeLuft = (c) => c[c.length - 1].gapEfter || 0;

  // Ett stycke som är högre än en hel sida (t.ex. ett jättelångt stycke text)
  // går inte att flytta helt. Då delas det i enskilda block, men en rubrik
  // eller ett kolumnhuvud följer fortfarande med blocket efter sig.
  const delaUpp = (c) => {
    const ut = [];
    let lim = [];
    for (const b of c) {
      lim.push(b);
      if (b.roll !== 'titel' && b.roll !== 'kolhuvud' && b.roll !== 'rubrik') { ut.push(lim); lim = []; }
    }
    if (lim.length) { if (ut.length) ut[ut.length - 1].push(...lim); else ut.push(lim); }
    return ut;
  };

  const pages = [];
  {
    const ko = [...chunks];
    let sida = null;
    const nySida = () => { sida = { stycken: [], h: 0 }; pages.push(sida); };
    nySida();
    while (ko.length) {
      const c = ko.shift();
      const h = styckeHojd(c);
      const tomSida = sida.stycken.length === 0;
      const extra = tomSida ? fortsHojd(c) : 0;
      if (extra + h > INNEHALL_HOJD && c.length > 1) {
        // Får aldrig plats ens på en tom sida — dela upp det.
        const delar = delaUpp(c);
        if (delar.length > 1) { ko.unshift(...delar); continue; }
      }
      if (!tomSida && sida.h + h > INNEHALL_HOJD) {
        nySida();
        sida.h = fortsHojd(c);
      } else if (tomSida) {
        sida.h = extra;
      }
      sida.stycken.push(c);
      sida.h += h + styckeLuft(c);
    }
  }
  // Tom offert: en tom innehållssida, som förut.
  if (!pages[pages.length - 1].stycken.length && pages.length > 1) pages.pop();

  // ---------- Produktblad (bilagor) ----------
  // Texten mäts på samma sätt, i den smalare spalten bredvid bilden. Blir den
  // för lång fortsätter produktbladet på nästa sida.
  const SHEET_BILD = 260, SHEET_GAP = 32;
  const sheetTextBredd = INNEHALL_BREDD - SHEET_BILD - SHEET_GAP;
  const sheetBlocks = [];
  sheetItems.forEach(item => {
    const rader = String(item.description || '').split(/\n+/).filter(l => l.trim());
    rader.forEach((rad, i) => sheetBlocks.push({ id: `sheet:${item.sku}:${i}`, item, rad }));
    if (!rader.length) sheetBlocks.push({ id: `sheet:${item.sku}:tom`, item, rad: '' });
  });

  const sheetPages = [];
  {
    let aktuell = null;
    for (const b of sheetBlocks) {
      const h = matt[b.id] != null ? matt[b.id] : 26;
      const forstaSidan = !aktuell || aktuell.item.sku !== b.item.sku;
      if (forstaSidan) {
        aktuell = { item: b.item, rader: [], forstaDel: true, h: 120 + (b.item.imageUrl ? 0 : 0) };
        sheetPages.push(aktuell);
      }
      // Första sidan har bilden bredvid texten; budgeten är densamma men texten
      // smalare, vilket mätningen redan tagit höjd för.
      const budget = INNEHALL_HOJD - 150;
      if (aktuell.h + h > budget && aktuell.rader.length) {
        aktuell = { item: b.item, rader: [], forstaDel: false, h: 120 };
        sheetPages.push(aktuell);
      }
      if (b.rad) aktuell.rader.push(b.rad);
      aktuell.h += h;
    }
  }

  const totalPages = 1 + pages.length + sheetPages.length;
  let pn = 0;
  const next = () => ++pn;

  return (
    <div className="offer-print-root" style={{padding:32, background:'var(--ulab-cool-grey)', display:'flex', flexDirection:'column', gap:24, alignItems:'center'}}>
      <style>{`
        .offer-print-root, .offer-print-root * {
          -webkit-print-color-adjust: exact !important;
          print-color-adjust: exact !important;
          color-adjust: exact !important;
        }
        @media print {
          @page { size: A4; margin: 0; }
          /* index.html sätter height:100% + overflow:hidden på body för
             app-layouten. Ligger det kvar vid utskrift skriver vissa webbläsare
             bara ut FÖRSTA sidan — därför släpps höjden fri här. */
          html, body {
            background:#fff !important;
            height:auto !important; min-height:0 !important; overflow:visible !important;
            -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important;
          }
          body * { visibility: hidden !important; }
          .offer-print-root, .offer-print-root * { visibility: visible !important; }
          .offer-matyta, .offer-matyta * { display: none !important; }
          .offer-print-root { position:absolute; left:0; top:0; padding:0 !important; gap:0 !important; background:#fff !important; }
          .offer-page {
            box-shadow: none !important;
            page-break-after: always; break-after: page;
            margin:0 !important;
            /* A4 är 1122,5 px vid 96 dpi — 1123 px spiller över med en halv
               pixel och kan ge en tom sida efter varje riktig sida. */
            height: 1122px !important;
            -webkit-print-color-adjust: exact !important;
            print-color-adjust: exact !important;
          }
          .offer-page:last-child { page-break-after: auto; }
          /* Vitt papper vid Skriv ut. Bara själva arket blir vitt —
             korallränderna, kundrutan på framsidan och summarutorna behåller
             sin färg. Klassen sätts bara för Skriv ut, inte för Spara som PDF.
             Ramarna mot den beige tonen byts mot en neutral grå, annars syns
             en beige kant mot vitt. */
          .utskrift-vitt .offer-page { background: #fff !important; }
          .utskrift-vitt .offer-page [style*="ece5d8"] { border-color: #d8d8d8 !important; }
        }
      `}</style>

      {/* Osynlig mätyta. Samma bredd och stilar som en riktig sida, men utanför
          skärmen — den syns aldrig och följer aldrig med till utskriften. */}
      <div className="offer-matyta" ref={matRef} aria-hidden="true" style={{
        position:'absolute', left:-10000, top:0, width:INNEHALL_BREDD,
        visibility:'hidden', pointerEvents:'none',
      }}>
        {/* display:flow-root gör att blockets egna marginaler räknas MED i
            höjden. Utan det mäts en rubrik eller ett stycke utan sin marginal,
            och sidan blev 30–90 px för hög — alltså klippt igen. Samma
            inpackning används på den riktiga sidan nedan, så måtten stämmer. */}
        {blocks.map(b => <div key={b.id} data-mid={b.id} style={{display:'flow-root'}}>{b.node}</div>)}
        {fortsMatt.map(b => <div key={b.id} data-mid={b.id} style={{display:'flow-root'}}>{b.node}</div>)}
        <div style={{width:sheetTextBredd}}>
          {sheetBlocks.map(b => (
            <div key={b.id} data-mid={b.id} style={{display:'flow-root'}}><SheetLine line={b.rad} /></div>
          ))}
        </div>
      </div>

      <Page n={next()} total={totalPages} hideHeader noPad>
        <div style={{display:'flex', flexDirection:'column', height:'100%', position:'relative'}}>
          <div style={{height:8, background:'var(--ulab-coral)'}}/>
          <div style={{padding:'40px 64px 0', display:'flex', justifyContent:'space-between', alignItems:'flex-start'}}>
            <img src="assets/ulab-logo.png?v=2" style={{height:46}} alt="ULAB"/>
            <div style={{textAlign:'right', fontSize:11, color:'var(--fg-muted)', lineHeight:1.7, letterSpacing:'.04em'}}>
              <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--ulab-blue)'}}>Offert</div>
              <div style={{color:'var(--ulab-navy)', fontWeight:700, fontSize:13, marginTop:2}}>#{offer.number}</div>
              <div style={{marginTop:2}}>{offer.date}</div>
            </div>
          </div>

          <div style={{padding:'56px 64px 40px', flex:'0 0 auto'}}>
            <div style={{fontSize:11, fontWeight:600, letterSpacing:'.22em', textTransform:'uppercase', color:'var(--ulab-coral)', marginBottom:18}}>
              Make IT easy
            </div>
            <h1 style={{fontFamily:'var(--font-serif)', fontWeight:500, fontSize:64, color:'var(--ulab-navy)', lineHeight:1.0, margin:'0 0 18px', letterSpacing:'-0.012em', maxWidth:'14ch'}}>
              {offer.subject || <React.Fragment>Vi är din trygga<br/>helhetsleverantör.</React.Fragment>}
            </h1>
            <div style={{height:3, width:64, background:'var(--ulab-coral)', margin:'24px 0 28px'}}/>
            <div style={{fontFamily:'var(--font-serif)', fontSize:24, color:'var(--ulab-navy)', lineHeight:1.45, fontWeight:400}}>
              Ett förslag framtaget för<br/>
              <b style={{fontWeight:600}}>{offer.customer || '—'}</b>
              {offer.contact && <span style={{fontFamily:'var(--font-sans)', fontSize:14, color:'var(--fg-muted)', display:'block', marginTop:8, fontWeight:400}}>Att: {offer.contact}</span>}
            </div>
          </div>

          <div style={{padding:'28px 64px 36px', background:'var(--ulab-cream)', borderTop:'1px solid #ede4d4', borderBottom:'1px solid #ede4d4'}}>
            <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:32}}>
              {[
                ['Personlig kontakt', 'Våra medarbetare kan tekniken vi säljer — och tar ansvar för att ditt kontor fungerar som det ska.'],
                ['Hela IT‑ansvaret', 'Nätverk, server, säkerhet, telefoni och arbetsplats — på ett avtal.'],
                ['Trygghet i drift', 'Övervakning, backup och säkerhet som är aktiv dygnet runt.'],
              ].map(([t, b]) => (
                <div key={t}>
                  <div style={{fontSize:11, fontWeight:600, letterSpacing:'.16em', textTransform:'uppercase', color:'var(--ulab-blue)', marginBottom:8}}>{t}</div>
                  <div style={{fontSize:13, color:'var(--ulab-navy)', lineHeight:1.55, fontFamily:'var(--font-sans)'}}>{b}</div>
                </div>
              ))}
            </div>
          </div>

          <div style={{flex:1}}/>

          <div style={{padding:'28px 64px 40px', display:'grid', gridTemplateColumns:'1fr 1fr', gap:40, borderTop:'1px solid #ede4d4'}}>
            <div>
              <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--fg-muted)', marginBottom:8}}>Din kontaktperson på Ulab IT Partner</div>
              <div style={{color:'var(--ulab-navy)', fontWeight:700, fontSize:16, fontFamily:'var(--font-serif)'}}>{offer.salesperson || 'Adam Bjarneteg'}</div>
              <div style={{fontSize:13, color:'var(--ulab-navy)', marginTop:4}}>{offer.salesphone || '0767 – 72 15 21'}</div>
              <div style={{fontSize:13, color:'var(--ulab-navy)'}}>{offer.salesemail || 'adam.bjarneteg@ulabitpartner.se'}</div>
            </div>
            <div style={{textAlign:'right'}}>
              <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--fg-muted)', marginBottom:8}}>Offertens giltighet</div>
              <div style={{color:'var(--ulab-navy)', fontWeight:700, fontSize:16, fontFamily:'var(--font-serif)'}}>{offer.valid || '—'}</div>
              <div style={{fontSize:13, color:'var(--fg-muted)', marginTop:4}}>Skapad {offer.date}</div>
              <div style={{fontSize:12, color:'var(--fg-muted)', marginTop:8}}>ULAB IT Partner AB · Borås & Örby</div>
            </div>
          </div>

          <div style={{height:8, background:'var(--ulab-coral)'}}/>
        </div>
      </Page>

      {pages.map((sida, pi) => {
        const blockPaSidan = sida.stycken.flat();
        // Börjar sidan mitt i ett avsnitt får den en forts.-rubrik — och mitt
        // i en tabell även kolumnhuvudet. Tidigare kollades i stället om sidan
        // hade NÅGON rubrik alls, så en sida som började med en lös tabellrad
        // men hade en annan rubrik längre ned fick ingen forts.-rubrik.
        const forsta = blockPaSidan[0];
        const fortsatter = forsta && forsta.roll !== 'titel' ? forsta : null;
        const tabellForts = fortsatter && (fortsatter.roll === 'rad' || fortsatter.roll === 'summa') && fortsatter.tabell;
        const fortsHuvud = tabellForts ? fortsMatt.find(f => f.id === `${fortsatter.tabell}:forts`) : null;
        return (
          <Page key={pi} n={next()} total={totalPages} customer={offer.customer} subject={offer.subject} nummer={offer.number}
            center={allaMatta && pages.length === 1 && blockPaSidan.reduce((h, b) => h + hojd(b), 0) < INNEHALL_HOJD * 0.72}>
            {fortsatter && <div style={{display:'flow-root'}}><SectionTitle title={`${fortsatter.titel} (forts.)`} /></div>}
            {fortsHuvud && <div style={{display:'flow-root'}}>{fortsHuvud.node}</div>}
            {blockPaSidan.map((b, i) => (
              <div key={b.id} style={{display:'flow-root', marginBottom: i < blockPaSidan.length - 1 ? (b.gapEfter || 0) : 0}}>{b.node}</div>
            ))}
          </Page>
        );
      })}

      {sheetPages.map((sida, idx) => (
        <ProductSheetPage key={`${sida.item.sku}-${idx}`} item={sida.item} rader={sida.rader}
          forstaDel={sida.forstaDel} n={next()} total={totalPages} nummer={offer.number}
          customer={offer.customer} subject={offer.subject} isFirst={idx === 0}/>
      ))}
    </div>
  );
}

// ---------- Byggstenar ----------
function SectionTitle({ title }) {
  return (
    <div>
      <h2 style={{fontFamily:'var(--font-serif)', fontWeight:500, fontSize:30, color:'var(--ulab-navy)', margin:'0 0 4px', lineHeight:1.15}}>{title}</h2>
      <div style={{height:2, width:36, background:'var(--ulab-coral)', margin:'0 0 22px'}}/>
    </div>
  );
}
// Kolumnhuvud för en produktkategori. Kategorinamnet till vänster och
// kolumnernas namn ovanför siffrorna — tidigare stod bara kategorin, och
// kunden fick gissa vad "10  Tim  1 250 kr  12 500 kr" betydde.
//
// Prismodellen styr kolumnerna. Kontant: À-pris · Summa. Finansierat: bara
// kr/mån · Per månad — köppriset finns inte med i dokumentet alls. Båda: alla
// fyra. Kolumnbredderna MÅSTE vara samma i huvud och rad.
const opProduktKolumner = (mode) => mode === 'bada' ? '1fr 50px 70px 80px 90px 80px 90px' : '1fr 60px 80px 90px 110px';
const opKolEtikett = { fontSize:10, fontWeight:600, letterSpacing:'.12em', textTransform:'uppercase', color:'var(--fg-muted)' };
function OpProduktHuvud({ cat, forts, mode }) {
  const kop = mode !== 'hyra', hyra = mode !== 'kontant';
  return (
    <div style={{display:'grid', gridTemplateColumns:opProduktKolumner(mode), gap:8, alignItems:'end', borderBottom:'1px solid var(--ulab-navy)', paddingBottom:6, marginBottom:2, marginTop:12}}>
      <span style={{fontSize:11, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--ulab-blue)'}}>{cat}{forts ? ' (forts.)' : ''}</span>
      <span style={{...opKolEtikett, textAlign:'right'}}>Antal</span>
      <span style={opKolEtikett}>Enhet</span>
      {kop && <span style={{...opKolEtikett, textAlign:'right'}}>À-pris</span>}
      {kop && <span style={{...opKolEtikett, textAlign:'right'}}>Summa</span>}
      {hyra && <span style={{...opKolEtikett, textAlign:'right'}}>kr/mån</span>}
      {hyra && <span style={{...opKolEtikett, textAlign:'right'}}>Per månad</span>}
    </div>
  );
}
function ProductRow({ l, mode }) {
  const kop = mode !== 'hyra', hyra = mode !== 'kontant';
  const perMan = l.monthly == null || l.qty == null ? null : l.qty * l.monthly;
  return (
    <div style={{padding:'8px 0', borderBottom:'1px solid #ece5d8'}}>
      <div style={{display:'grid', gridTemplateColumns:opProduktKolumner(mode), gap:8, fontSize:13, color:'var(--ulab-navy)'}}>
        <span style={{fontWeight:600}}>{l.name || '—'}</span>
        <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{l.qty}</span>
        <span style={{color:'var(--fg-muted)'}}>{l.unit}</span>
        {kop && <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{fmtKr(l.price)}</span>}
        {kop && <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight: hyra ? 500 : 600}}>{fmtKrOrIngar(l.price == null || l.qty == null ? null : l.qty*l.price)}</span>}
        {hyra && <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{l.monthly == null ? '—' : fmtKr(l.monthly)}</span>}
        {hyra && <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight:600}}>{perMan == null ? '—' : fmtKrOrIngar(perMan) + (perMan ? '/mån' : '')}</span>}
      </div>
      {l.desc && <div style={{fontSize:12, color:'var(--fg-muted)', marginTop:4, lineHeight:1.5, maxWidth:'70%'}}>{l.desc}</div>}
    </div>
  );
}
// Utan rabatt var "Produkter & tjänster" och "Summa exkl. moms" samma siffra
// två gånger. Nu visas mellanraderna bara när det finns en rabatt att visa.
function ProductSummary({ offer, sub, disc, net, mode, hyraPerManad }) {
  return (
    <div style={{marginTop:14, marginLeft:'auto', maxWidth:360, fontVariantNumeric:'tabular-nums', padding:'10px 14px', background:'var(--ulab-cream)', borderRadius:4}}>
      {mode !== 'hyra' && disc>0 && <PRow label="Produkter & tjänster" v={fmtKr(sub)}/>}
      {mode !== 'hyra' && disc>0 && <PRow label={`Rabatt ${offer.discount}%`} v={'– '+fmtKr(disc)}/>}
      {mode !== 'hyra' && disc>0 && <div style={{height:1, background:'var(--border-strong)', margin:'6px 0'}}/>}
      {mode !== 'hyra' && <PRow big label={<React.Fragment>{mode === 'bada' ? 'Vid köp' : 'Summa'} <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(net)}/>}
      {mode !== 'kontant' && <PRow big label={<React.Fragment>{mode === 'bada' ? 'Vid hyra, per månad' : 'Totalt per månad'} <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(hyraPerManad)}/>}
    </div>
  );
}
function OpMoms({ text }) {
  return <span style={{fontSize:11, fontWeight:500, color:'var(--fg-muted)', marginLeft:4}}>{text}</span>;
}
// Enheten på en tjänst är perioden ("månad", "år"). Den stod tidigare i en
// egen kolumn direkt efter antalet, så "14  månad" lästes som 14 månader fast
// det var 14 licenser. Nu står perioden efter priset: "211 kr/mån".
function opKortEnhet(u) {
  const t = String(u || 'månad').trim();
  if (/^m(å|a)n/i.test(t)) return 'mån';
  if (/^år/i.test(t)) return 'år';
  if (/^kv/i.test(t)) return 'kvartal';
  return t;
}
const opTjanstKolumner = '1fr 60px 130px 140px';
function OpTjanstHuvud({ perManad }) {
  return (
    <div style={{display:'grid', gridTemplateColumns:opTjanstKolumner, gap:8, alignItems:'end', borderBottom:'1px solid var(--ulab-navy)', paddingBottom:6, marginBottom:2}}>
      <span style={opKolEtikett}>Tjänst</span>
      <span style={{...opKolEtikett, textAlign:'right'}}>Antal</span>
      <span style={{...opKolEtikett, textAlign:'right'}}>À-pris</span>
      <span style={{...opKolEtikett, textAlign:'right'}}>{perManad ? 'Per månad' : 'Summa'}</span>
    </div>
  );
}
function ServiceRow({ l }) {
  const enh = opKortEnhet(l.unit);
  return (
    <div style={{padding:'10px 0', borderBottom:'1px solid #ece5d8'}}>
      <div style={{display:'grid', gridTemplateColumns:opTjanstKolumner, gap:8, fontSize:13, color:'var(--ulab-navy)'}}>
        <span style={{fontWeight:600}}>{l.name||'—'}</span>
        <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{l.qty}</span>
        <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums'}}>{fmtKr(l.price)}/{enh}</span>
        <span style={{textAlign:'right', fontVariantNumeric:'tabular-nums', fontWeight:600}}>{fmtKrOrIngar(l.price == null || l.qty == null ? null : l.qty*l.price)}{l.price && l.qty ? '/' + enh : ''}</span>
      </div>
      {l.desc && <div style={{fontSize:12, color:'var(--fg-muted)', marginTop:4, lineHeight:1.5, maxWidth:'70%'}}>{l.desc}</div>}
    </div>
  );
}
function ServiceSummary({ services, perManad }) {
  return (
    <div style={{marginTop:14, padding:'10px 14px', background:'var(--ulab-cream)', borderRadius:4, marginLeft:'auto', maxWidth:360, fontVariantNumeric:'tabular-nums'}}>
      <PRow big label={<React.Fragment>{perManad ? 'Totalt per månad' : 'Totalt per period'} <OpMoms text="exkl. moms"/></React.Fragment>}
        v={fmtKr(services.reduce((s,l)=>s+(l.qty||0)*(l.price||0),0))}/>
    </div>
  );
}
function Tom({ text }) {
  return <div style={{fontSize:13, color:'var(--fg-muted)'}}>{text}</div>;
}
// Ett stycke löptext. Listor och rubriker tolkas av kiTolka (KlistraIn.jsx)
// innan de kommer hit, så här finns bara text — med eventuell **fet** stil.
function TextBlock({ text }) {
  return <p style={{margin:'0 0 14px', fontFamily:'var(--font-sans)', fontSize:14, color:'var(--ulab-navy)', lineHeight:1.7, maxWidth:'66ch'}}><KiText text={text}/></p>;
}
// Rubriker och punkter inne i ett avsnitts text. Typsnitt och färger är
// alltid ULAB:s — ingenting följer med från Word.
function OpRubrik({ text }) {
  return <h3 style={{fontFamily:'var(--font-serif)', fontWeight:500, fontSize:21, color:'var(--ulab-navy)', margin:'8px 0 10px', lineHeight:1.25}}><KiText text={text}/></h3>;
}
function OpUnderrubrik({ text }) {
  return <h4 style={{fontFamily:'var(--font-sans)', fontWeight:700, fontSize:14.5, color:'var(--ulab-navy)', margin:'6px 0 6px', lineHeight:1.4, letterSpacing:'.005em'}}><KiText text={text}/></h4>;
}
function OpPunkt({ text, sist }) {
  return (
    <div style={{display:'flex', gap:10, fontFamily:'var(--font-sans)', fontSize:14, color:'var(--ulab-navy)', lineHeight:1.7, marginBottom: sist ? 14 : 2, paddingLeft:4, maxWidth:'66ch'}}>
      <span aria-hidden="true" style={{color:'var(--ulab-coral)', flex:'none'}}>•</span>
      <span><KiText text={text}/></span>
    </div>
  );
}
function SheetLine({ line }) {
  if (!line) return <div style={{height:0}}/>;
  const isBullet = /^\s*[•\-*]/.test(line);
  return isBullet
    ? <div style={{display:'flex', gap:8, fontSize:14, color:'var(--ulab-navy)', lineHeight:1.7, marginBottom:4}}><span style={{color:'var(--ulab-coral)'}}>•</span><span>{line.replace(/^\s*[•\-*]\s*/, '')}</span></div>
    : <p style={{margin:'0 0 10px', fontSize:14, color:'var(--ulab-navy)', lineHeight:1.7}}>{line}</p>;
}

// En bilagesida per vald produkt — bild + beskrivning från Produktblad-panelen.
function ProductSheetPage({ item, rader, forstaDel, n, total, customer, subject, nummer, isFirst }) {
  return (
    <Page n={n} total={total} customer={customer} subject={subject} nummer={nummer}>
      {isFirst && (
        <div style={{fontSize:11, fontWeight:600, letterSpacing:'.22em', textTransform:'uppercase', color:'var(--ulab-coral)', marginBottom:18}}>
          Bilaga — Produktblad
        </div>
      )}
      <h2 style={{fontFamily:'var(--font-serif)', fontWeight:500, fontSize:30, color:'var(--ulab-navy)', margin:'0 0 4px', lineHeight:1.15}}>
        {(item.name || '—') + (forstaDel ? '' : ' (forts.)')}
      </h2>
      <div style={{height:2, width:36, background:'var(--ulab-coral)', margin:'0 0 22px'}}/>
      <div style={{fontSize:11, color:'var(--fg-muted)', marginBottom:24}}>
        {item.sku}{item.cat ? ` · ${item.cat}` : ''}{item.qty ? ` · ${item.qty} ${item.unit||'st'} i offerten` : ''}
      </div>
      <div style={{display:'flex', gap:32, alignItems:'flex-start'}}>
        {item.imageUrl && forstaDel && (
          <img src={item.imageUrl} alt="" style={{width:260, height:260, objectFit:'contain', background:'#fff', border:'1px solid #ece5d8', borderRadius:6, flexShrink:0}}/>
        )}
        <div style={{flex:1, minWidth:0}}>
          {(rader || []).map((line, i) => <SheetLine key={i} line={line} />)}
        </div>
      </div>
    </Page>
  );
}

function Page({children, n, total, customer, subject, nummer, hideHeader, noPad, center}) {
  return (
    <div className="offer-page" style={{
      width: SIDA_BREDD, height: SIDA_HOJD, background:'var(--ulab-cream)',
      boxShadow:'0 2px 4px rgba(0,54,95,.06),0 14px 36px rgba(0,54,95,.10)',
      padding: noPad ? 0 : `${SIDA_PAD_TOPP}px ${SIDA_PAD_X}px ${SIDA_PAD_BOTTEN}px`, position:'relative',
      display:'flex', flexDirection:'column', boxSizing:'border-box', overflow:'hidden',
    }}>
      {!hideHeader && (
        <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', paddingBottom:14, marginBottom:24, borderBottom:'1px solid #ece5d8', fontSize:11, color:'var(--fg-muted)'}}>
          <span><b style={{color:'var(--ulab-navy)'}}>{customer || '—'}</b>{subject ? ` · ${subject}` : ''}</span>
          {/* Offertnumret står på varje sida, så lösa sidor går att koppla ihop. */}
          <span style={{whiteSpace:'nowrap'}}>{nummer ? <React.Fragment>Offert #{nummer} · </React.Fragment> : null}Sida {n} av {total}</span>
        </div>
      )}
      <div style={{flex:1, display:'flex', flexDirection:'column', justifyContent: center?'center':'flex-start'}}>{children}</div>
      {!hideHeader && (
        <div style={{position:'absolute', bottom:24, left:64, right:64, paddingTop:14, borderTop:'1px solid #ece5d8', display:'flex', justifyContent:'space-between', fontSize:10, color:'var(--fg-muted)'}}>
          <span>ulabitpartner.se · 020 – 10 45 80</span>
          <span>ULAB Borås · ULAB Örby</span>
        </div>
      )}
    </div>
  );
}

function CostSummary({ offer, sub, disc, net, vat, total, mode, term, hyraPerManad, totaltPerManad, services, monthlyServiceTotal, body }) {
  // Speglar TotalsPanel i editorn (steg 3) exakt — samma fält, samma ordning.
  const finansiar = offer.financier || 'De Lage Landen';
  const kop = mode !== 'hyra', hyra = mode !== 'kontant';
  return (
    <div>
      {body && <div style={{marginBottom:18, fontFamily:'var(--font-sans)', fontSize:14, color:'var(--ulab-navy)', lineHeight:1.7, maxWidth:'66ch'}}>{body}</div>}
      <div style={{display:'flex', justifyContent:'flex-end'}}>
        <div style={{
          width:380, padding:'22px 26px', background:'var(--ulab-cream)', borderRadius:6,
          fontVariantNumeric:'tabular-nums', fontFamily:'var(--font-sans)',
        }}>
          {kop && (
            <React.Fragment>
              {mode === 'bada' && <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--ulab-coral)', marginBottom:6}}>Alternativ 1 — köp</div>}
              {/* Utan rabatt var Summa och Netto samma siffra två gånger. Och "Att
                  betala" stod utan moms-angivelse, direkt ovanför raden "Alla
                  priser anges exklusive moms" — fast just den siffran är inklusive. */}
              {disc>0 ? (
                <React.Fragment>
                  <PRow label="Summa" v={fmtKr(sub)}/>
                  <PRow label={`Rabatt ${offer.discount}%`} v={'– '+fmtKr(disc)}/>
                  <PRow label={<React.Fragment>Netto <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(net)}/>
                </React.Fragment>
              ) : (
                <PRow label={<React.Fragment>Summa <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(net)}/>
              )}
              <PRow label="Moms 25%" v={fmtKr(vat)}/>
              <div style={{height:1, background:'var(--border-strong)', margin:'10px 0'}}/>
              <PRow big label={<React.Fragment>Att betala <OpMoms text="inkl. moms"/></React.Fragment>} v={fmtKr(total)}/>
            </React.Fragment>
          )}
          {hyra && (
            // Finansierat: bara månadsbelopp, ingen köpsumma och ingen moms —
            // kunden är ett företag och finansiärens avtal anges exkl. moms.
            <div style={{marginTop: kop ? 16 : 0, paddingTop: kop ? 14 : 0, borderTop: kop ? '1px dashed var(--border-strong)' : 'none'}}>
              {mode === 'bada' && <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--ulab-coral)', marginBottom:6}}>Alternativ 2 — hyra</div>}
              <PRow label={`Hyra via ${finansiar}`} v={fmtKr(hyraPerManad) + '/mån'}/>
              {services.length>0 && <PRow label="Tjänster från ULAB IT Partner" v={fmtKr(monthlyServiceTotal) + '/mån'}/>}
              <div style={{height:1, background:'var(--border-strong)', margin:'10px 0'}}/>
              <PRow big label={<React.Fragment>Totalt per månad <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(totaltPerManad)}/>
              <div style={{marginTop:8, fontSize:12, color:'var(--ulab-navy)'}}>
                Avtalstid <b>{term} månader</b> · finansieras via {finansiar}
              </div>
            </div>
          )}
          {!hyra && services.length>0 && (
            <div style={{marginTop:14, paddingTop:12, borderTop:'1px dashed var(--border-strong)'}}>
              <div style={{fontSize:10, fontWeight:600, letterSpacing:'.18em', textTransform:'uppercase', color:'var(--ulab-coral)', marginBottom:6}}>Månadskostnader</div>
              {services.map(l => (
                <div key={l.id} style={{display:'flex', justifyContent:'space-between', fontSize:13, color:'var(--ulab-navy)', padding:'2px 0'}}>
                  <span>{l.name || '—'}{l.qty>1 && <span style={{color:'var(--fg-muted)'}}> × {l.qty}</span>}</span>
                  <span>{fmtKrOrIngar((l.qty||0)*(l.price||0))}{(l.qty||0)*(l.price||0) ? '/' + opKortEnhet(l.unit) : ''}</span>
                </div>
              ))}
              <div style={{height:1, background:'var(--border-strong)', margin:'6px 0'}}/>
              <PRow big label={<React.Fragment>Per månad <OpMoms text="exkl. moms"/></React.Fragment>} v={fmtKr(monthlyServiceTotal)}/>
            </div>
          )}
        </div>
      </div>
      <div style={{marginTop:14, fontSize:11, color:'var(--fg-muted)', lineHeight:1.6}}>
        Alla priser anges exklusive moms där inget annat anges. Frakt och eventuell resa enligt avtal.
      </div>
    </div>
  );
}
function PRow({label, v, big}) {
  return (
    <div style={{display:'flex', justifyContent:'space-between', padding:'4px 0', fontSize: big?16:13, fontWeight: big?700:500, color:'var(--ulab-navy)'}}>
      <span>{label}</span><span>{v}</span>
    </div>
  );
}
window.OfferPreview = OfferPreview;
