/* ============================================================
   Modport — Product configurator (React)
   ============================================================ */
const { useState, useEffect, useMemo, useRef } = React;

// ---------- Crossfade image (two-layer swap) ----------
// Keeps the previous image visible while the new one fades in over it,
// then drops the old layer. Avoids the hard cut you'd get from `key`-only swap.
function CrossfadeImage({ src, alt, className, style }) {
  const [layers, setLayers] = useState(() => [{ id: 0, src, state: 'visible' }]);
  const nextId = useRef(1);

  useEffect(() => {
    setLayers(prev => {
      const top = prev[prev.length - 1];
      if (top && top.src === src) return prev;
      // Mark all currently-visible layers as fading-out, push new layer fading-in
      return [
        ...prev.map(l => ({ ...l, state: 'fading-out' })),
        { id: nextId.current++, src, state: 'fading-in' }
      ];
    });
  }, [src]);

  return layers.map((l) => (
    <img
      key={l.id}
      src={l.src}
      alt={alt}
      className={[className, l.state === 'fading-in' ? 'fading-in' : l.state === 'fading-out' ? 'fading-out' : ''].filter(Boolean).join(' ')}
      style={style}
      onAnimationEnd={() => {
        if (l.state === 'fading-out') {
          setLayers(prev => prev.filter(x => x.id !== l.id));
        } else if (l.state === 'fading-in') {
          setLayers(prev => prev.map(x => x.id === l.id ? { ...x, state: 'visible' } : x));
        }
      }}
    />
  ));
}

// ---------- Tillval-angle image lookup ----------
// At the tillval/charger-battery camera angle (Camera_ChargerBattery_S).
// Door open when battery=on, closed when battery=off (rule B).
// Filename: s-<color>-<walls>[-shed][-<addons>]-tillval.webp
function tillvalImage(model, color, walls, shed, evCharger, battery) {
  if (model !== 's') return null;
  // Build addon segment: 'c' (charger), 'b' (battery), 'cb' (both), '' (none)
  const addons = (evCharger && battery) ? 'cb' : (evCharger ? 'c' : (battery ? 'b' : ''));
  const parts = ['s', color, walls];
  if (shed) parts.push('shed');
  if (addons) parts.push(addons);
  parts.push('tillval');
  return parts.join('-') + '.webp';
}

// ---------- Hero (side/top) angle image lookup ----------
// Filename: s-<color>-<walls>[-shed][-<addons>]-hero-<side|top>.webp
// For TOP angle, addons are ignored (roof + walls hide them from above) —
// except for walled+shed-off where a dedicated "with addons" render exists
// showing the charger cable run.
function heroImage(model, color, walls, shed, evCharger, battery, rotation) {
  if (model !== 's') return null;
  const addons = (evCharger && battery) ? 'cb' : (evCharger ? 'c' : (battery ? 'b' : ''));

  if (rotation === 'top') {
    // Top-angle addon visibility per (walls × shed):
    //   walled+noshed → only charger visible (cable on right post). Battery NOT visible.
    //   walled+shed   → nothing visible (roof + walls cover).
    //   open+noshed   → both charger AND battery visible.
    //   open+shed     → only charger visible. Battery covered by shed roof.
    const parts = ['s', color, walls];
    if (shed) parts.push('shed');
    let suffix = '';
    if (!shed && walls === 'walled') {
      if (evCharger) suffix = 'c';
    } else if (!shed && walls === 'open') {
      suffix = (evCharger && battery) ? 'cb' : (evCharger ? 'c' : (battery ? 'b' : ''));
    } else if (shed && walls === 'open') {
      if (evCharger) suffix = 'c';
    }
    if (suffix) parts.push(suffix);
    parts.push('hero-top');
    return parts.join('-') + '.webp';
  }

  // SIDE angle — addons matter (visible from this camera).
  const parts = ['s', color, walls];
  if (shed) parts.push('shed');
  if (addons) parts.push(addons);
  parts.push('hero-side');
  return parts.join('-') + '.webp';
}

// ---------- Model D image resolver ----------
// Filename: d-<color>-<roof>-<walls>-<shed>[-<addons>]-<view>.webp
// Addon availability per view mirrors the render batch (batch_gable.py):
//  - walled aerial → only the no-addon ('') render exists (roof + walls cover everything)
//  - everything else → all 4 addon combos exist
//    (for walled+shed hero/front/rear: shed door opens for b/cb so battery is visible)
function dImage(color, roof, walls, shedSeg, evCharger, battery, view) {
  const full = (evCharger && battery) ? 'cb' : (evCharger ? 'c' : (battery ? 'b' : ''));
  const eff = (walls === 'walled' && view === 'aerial') ? '' : full;
  const parts = ['d', color, roof, walls, shedSeg];
  if (eff) parts.push(eff);
  parts.push(view);
  return parts.join('-') + '.webp';
}

// Camera-view button labels (Model D exposes the 5 exterior angles we render).
const VIEW_LABELS = {
  side:   { sv: 'SIDA',   en: 'SIDE' },
  top:    { sv: 'TOPP',   en: 'TOP' },
  hero:   { sv: 'VINKEL', en: 'ANGLE' },
  front:  { sv: 'FRAM',   en: 'FRONT' },
  aerial: { sv: 'OVAN',   en: 'AERIAL' },
  rear:   { sv: 'BAK',    en: 'REAR' },
  rearwall: { sv: 'VÄGG', en: 'WALL' }
};

// ---------- Model + option data ----------
const MODELS = {
  s: {
    name: 'Modport S',
    type_sv: 'Enplats', type_en: 'Single bay',
    base: 129000,
    hasRenders: true,  // images live at renders/modport-s-<color>-<view>.webp
    specs: [
      { v: '6,4', sup: 'kWp', l_sv: 'Effekt', l_en: 'Output' },
      { v: '17',  sup: 'm²',  l_sv: 'Yta',    l_en: 'Area' },
      { v: '1',   sup: '',    l_sv: 'Bilplats', l_en: 'Bay' }
    ],
    panels: [
      { id: 'std', name_sv: '6,4 kWp · 16 paneler', name_en: '6.4 kWp · 16 panels', delta: 0,
        desc_sv: 'Standard. ~5 800 kWh/år', desc_en: 'Standard. ~5,800 kWh/yr' },
      { id: 'hi',  name_sv: '7,2 kWp · 18 paneler', name_en: '7.2 kWp · 18 panels', delta: 14000,
        desc_sv: 'Utökat. ~6 500 kWh/år', desc_en: 'Extended. ~6,500 kWh/yr' }
    ]
  },
  d: {
    name: 'Modport D',
    type_sv: 'Tvåplats', type_en: 'Double bay',
    base: 189000,
    hasRenders: true,  // all 3 roofs (gable/mono/flat) fully rendered — renders/model d white/d-vit-<roof>-<walls>-<shed>[-addons]-<view>.webp (444 files, white)
    specs: [
      { v: '12,8', sup: 'kWp', l_sv: 'Effekt', l_en: 'Output' },
      { v: '32',   sup: 'm²',  l_sv: 'Yta',    l_en: 'Area' },
      { v: '2',    sup: '',    l_sv: 'Bilplatser', l_en: 'Bays' }
    ],
    panels: [
      { id: 'std', name_sv: '12,8 kWp · 32 paneler', name_en: '12.8 kWp · 32 panels', delta: 0,
        desc_sv: 'Standard. ~11 500 kWh/år', desc_en: 'Standard. ~11,500 kWh/yr' },
      { id: 'hi',  name_sv: '14,4 kWp · 36 paneler', name_en: '14.4 kWp · 36 panels', delta: 18000,
        desc_sv: 'Utökat. ~12 800 kWh/år', desc_en: 'Extended. ~12,800 kWh/yr' }
    ]
  }
};

// Cache-buster: bump this whenever renders are re-exported so browsers fetch the
// new files instead of a cached copy. Appended as ?v=… to every render URL.
const RENDER_VERSION = '2026-06-21';

const COLORS = [
  { id: 'vit',    name_sv: 'Vit',       name_en: 'White',      hex: '#ffffff', delta: 0 },
  { id: 'anth',   name_sv: 'Antracit',  name_en: 'Anthracite', hex: '#2a2a28', delta: 0 },
  { id: 'sienna', name_sv: 'Falu',      name_en: 'Falu Red',   hex: '#7B2321', delta: 4000 }
];

const ROOFS = [
  { id: 'gable', name_sv: 'Sadeltak · 35°',  name_en: 'Gable · 35°',     delta: 0,
    desc_sv: 'Klassisk siluett. Maximal takhöjd och snöavrinning.', desc_en: 'Classic silhouette. Maximum height and snow runoff.' },
  { id: 'mono',  name_sv: 'Pulpettak · 14°', name_en: 'Monopitch · 14°', delta: 0,
    desc_sv: 'Enkelsluttande tak som riktar panelerna mot söder.', desc_en: 'Single slope that orients the panels south.' },
  { id: 'flat',  name_sv: 'Platt tak · 5°',  name_en: 'Flat · 5°',       delta: 0,
    desc_sv: 'Diskret, låg profil. Modern look.', desc_en: 'Discreet low profile. Modern look.' }
];

const WALLS = [
  { id: 'open',   name_sv: 'Öppen', name_en: 'Open',   delta: 0,
    desc_sv: 'Öppna sidor. Mer luft och ljus.', desc_en: 'Open sides. More air and light.' },
  { id: 'walled', name_sv: 'Vägg',  name_en: 'Walled', delta: 14000,
    desc_sv: 'Plankvägg på sida och bak. Mer skydd och avskildhet.', desc_en: 'Plank wall on side and back. More shelter and privacy.' }
];

// Which finishes (and, for Model D, which roofs) actually have rendered images.
// Picking an unrendered combo would show broken images, so the configurator only
// offers what exists. Model S is a single flat roof — its finishes are not roof-gated.
// Update this map whenever a new color/roof set is rendered.
const RENDERED = {
  s: { vit: true, anth: true, sienna: true },              // all finishes rendered (41 each)
  d: {
    vit:    ['gable', 'mono', 'flat'],                     // 444 white renders, all roofs
    sienna: ['gable', 'mono', 'flat']                      // 444 red renders, all roofs (2026-06-17)
    // anth: not yet rendered for Model D — omitted, so it isn't offered on D
  }
};
function availableColors(model) {
  return COLORS.filter(c => RENDERED[model] && RENDERED[model][c.id]);
}
function roofAvailable(model, color, roofId) {
  const a = RENDERED[model] && RENDERED[model][color];
  return Array.isArray(a) ? a.includes(roofId) : !!a;
}


const SHED_PRICE = { small: 28000, big: 58000 };   // TODO: confirm real pricing
function shedDLabel(v, lang) {
  if (v === 'noshed') return lang === 'en' ? 'None' : 'Inget';
  if (v === 'big')    return lang === 'en' ? 'Large' : 'Stort';
  const place = v === 'smallside' ? (lang === 'en' ? 'Side' : 'Sida') : (lang === 'en' ? 'Back' : 'Bak');
  return (lang === 'en' ? 'Small · ' : 'Litet · ') + place;
}

const fmt = (n, lang) => {
  if (lang === 'en') return n.toLocaleString('en-US');
  return n.toLocaleString('sv-SE').replace(/,/g, ' ');
};

// ---------- Components ----------
function Spec({ s, lang }) {
  return (
    <div className="item">
      <div className="v" style={s.muted ? { color: 'var(--text-3)' } : null}>
        {s.v}{s.sup && <sup>{s.sup}</sup>}
      </div>
      <div className="l">{lang === 'en' ? s.l_en : s.l_sv}</div>
    </div>
  );
}

function ModelSwitcher({ model, setModel, lang }) {
  const list = ['s', 'd'];
  return (
    <div className="section-block">
      <div className="section-label">
        <span>{lang === 'en' ? 'Model' : 'Modell'}</span>
      </div>
      <div className="opt-grid">
        {list.map(id => {
          const m = MODELS[id];
          const selected = id === model;
          const displayName = m.name;
          return (
            <button key={id}
                    className={`opt ${selected ? 'selected' : ''}`}
                    onClick={() => setModel(id)}>
              <div className="top">
                <span className="name">{displayName}</span>
                <span className="price">{lang === 'en' ? 'From ' : 'Från '}{fmt(m.base, lang)} kr</span>
              </div>
              <span className="desc">{lang === 'en' ? m.type_en : m.type_sv}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function PaymentTabs({ pay, setPay, lang }) {
  const tabs = [
    { id: 'cash',    sv: 'Kontant',     en: 'Cash' },
    { id: 'lease',   sv: 'Leasing',     en: 'Lease' },
    { id: 'finance', sv: 'Finansiering', en: 'Finance' }
  ];
  return (
    <div className="pay-tabs">
      {tabs.map(t => (
        <button key={t.id}
                className={pay === t.id ? 'active' : ''}
                onClick={() => setPay(t.id)}>
          {lang === 'en' ? t.en : t.sv}
        </button>
      ))}
    </div>
  );
}

function PanelChoice({ model, value, setValue, lang }) {
  const m = MODELS[model];
  if (!m.panels) return null;
  return (
    <div className="section-block">
      <div className="section-label">
        <span>{lang === 'en' ? 'Solar capacity' : 'Soleffekt'}</span>
        <span className="val">
          {(m.panels.find(p => p.id === value) || m.panels[0])[lang === 'en' ? 'name_en' : 'name_sv']}
        </span>
      </div>
      <div className="opt-grid">
        {m.panels.map(p => (
          <button key={p.id}
                  className={`opt ${value === p.id ? 'selected' : ''}`}
                  onClick={() => setValue(p.id)}>
            <div className="top">
              <span className="name">{lang === 'en' ? p.name_en : p.name_sv}</span>
              <span className="price">{p.delta === 0 ? (lang === 'en' ? 'Included' : 'Ingår') : '+ ' + fmt(p.delta, lang) + ' kr'}</span>
            </div>
            <span className="desc">{lang === 'en' ? p.desc_en : p.desc_sv}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function WallChoice({ value, setValue, lang }) {
  return (
    <div className="section-block">
      <div className="section-label">
        <span>{lang === 'en' ? 'Walls' : 'Väggar'}</span>
        <span className="val">
          {(WALLS.find(w => w.id === value) || WALLS[0])[lang === 'en' ? 'name_en' : 'name_sv']}
        </span>
      </div>
      <div className="opt-grid">
        {WALLS.map(w => (
          <button key={w.id}
                  className={`opt ${value === w.id ? 'selected' : ''}`}
                  onClick={() => setValue(w.id)}>
            <div className="top">
              <span className="name">{lang === 'en' ? w.name_en : w.name_sv}</span>
              <span className="price">{w.delta === 0 ? (lang === 'en' ? 'Included' : 'Ingår') : '+ ' + fmt(w.delta, lang) + ' kr'}</span>
            </div>
            <span className="desc">{lang === 'en' ? w.desc_en : w.desc_sv}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

function ColorChoice({ value, setValue, lang, model }) {
  const cols = availableColors(model);
  const c = cols.find(x => x.id === value) || cols[0];
  return (
    <div className="section-block">
      <div className="section-label">
        <span>{lang === 'en' ? 'Finish' : 'Färg'}</span>
        <span className="val">{lang === 'en' ? c.name_en : c.name_sv}</span>
      </div>
      <div className="swatches">
        {cols.map(col => (
          <div key={col.id}
               className={`swatch ${col.id === value ? 'selected' : ''}`}
               style={{ backgroundColor: col.hex }}
               onClick={() => setValue(col.id)}
               title={col.name_sv}>
          </div>
        ))}
      </div>
    </div>
  );
}

function RoofChoice({ value, setValue, lang, model, color }) {
  return (
    <div className="section-block">
      <div className="section-label">
        <span>{lang === 'en' ? 'Roof style' : 'Taklutning'}</span>
      </div>
      <div className="opt-grid">
        {ROOFS.map(r => {
          const avail = roofAvailable(model, color, r.id);
          return (
            <button key={r.id}
                    className={`opt ${value === r.id ? 'selected' : ''} ${avail ? '' : 'opt-soon'}`}
                    disabled={!avail}
                    onClick={() => avail && setValue(r.id)}>
              <div className="top">
                <span className="name">{lang === 'en' ? r.name_en : r.name_sv}</span>
                <span className="price">{!avail ? (lang === 'en' ? 'Soon' : 'Snart') : (r.delta === 0 ? (lang === 'en' ? 'Included' : 'Ingår') : '+ ' + fmt(r.delta, lang) + ' kr')}</span>
              </div>
              <span className="desc">{lang === 'en' ? r.desc_en : r.desc_sv}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function ToggleAddon({ active, setActive, title, desc, price, lang }) {
  return (
    <div className={`toggle-row ${active ? 'active' : ''}`}
         onClick={() => setActive(!active)}>
      <div className="info">
        <span className="name">{title}</span>
        <span className="desc">{desc}</span>
      </div>
      <div className="right">
        <span className="price">{price === 0 ? (lang === 'en' ? 'Included' : 'Ingår') : '+ ' + fmt(price, lang) + ' kr'}</span>
        <span className="toggle-switch"></span>
      </div>
    </div>
  );
}

// ---------- Main App ----------
function App() {
  const initial = new URLSearchParams(window.location.search).get('model');
  const validInitial = ['s','d'].includes(initial) ? initial : 's';

  const [model, setModel] = useState(validInitial);
  const [pay, setPay] = useState('cash');
  const [color, setColor] = useState('vit');
  const [roof, setRoof] = useState('gable');
  const [panel, setPanel] = useState('std');
  const [walls, setWalls] = useState('open');
  const [evCharger, setEv] = useState(false);
  const [battery, setBattery] = useState(false);
  const [shed, setShed] = useState(false);
  const [shedD, setShedD] = useState('noshed');   // Model D: noshed|smallback|smallside|big
  const [lang, setLang] = useState(window.Modport ? window.Modport.getLang() : 'sv');
  const [breakdownOpen, setBreakdownOpen] = useState(false);

  useEffect(() => {
    const onLang = (e) => setLang(e.detail.lang);
    document.addEventListener('langchange', onLang);
    return () => document.removeEventListener('langchange', onLang);
  }, []);

  // Keep color + roof valid for the current model — never sit on an unrendered combo.
  useEffect(() => {
    const cols = availableColors(model);
    if (!cols.find(c => c.id === color)) { setColor(cols[0] ? cols[0].id : 'vit'); return; }
    if (model === 'd' && !roofAvailable(model, color, roof)) {
      const firstRoof = ROOFS.find(r => roofAvailable(model, color, r.id));
      if (firstRoof) setRoof(firstRoof.id);
    }
  }, [model, color, roof]);

  const m = MODELS[model];

  // Total calc
  const breakdown = useMemo(() => {
    const rows = [];
    rows.push({ label_sv: m.name + ' · ' + m.type_sv, label_en: (m.name_en || m.name) + ' · ' + m.type_en, value: m.base });

    const w = WALLS.find(x => x.id === walls);
    if (w && w.delta) rows.push({ label_sv: 'Väggar · ' + w.name_sv, label_en: 'Walls · ' + w.name_en, value: w.delta });
    const r = ROOFS.find(x => x.id === roof);
    if (r && r.delta) rows.push({ label_sv: r.name_sv, label_en: r.name_en, value: r.delta });

    const c = COLORS.find(x => x.id === color);
    if (c && c.delta) rows.push({ label_sv: 'Färg · ' + c.name_sv, label_en: 'Finish · ' + c.name_en, value: c.delta });

    if (model === 'd') {
      if (shedD === 'big')        rows.push({ label_sv: 'Stort förråd · +12 paneler', label_en: 'Large shed · +12 panels', value: SHED_PRICE.big });
      else if (shedD === 'smallback' || shedD === 'smallside')
        rows.push({ label_sv: 'Litet förråd · ' + (shedD === 'smallside' ? 'sida' : 'bak'),
                     label_en: 'Small shed · ' + (shedD === 'smallside' ? 'side' : 'back'), value: SHED_PRICE.small });
    } else if (shed) {
      rows.push({ label_sv: 'Förråd · 1,85m + 3 paneler', label_en: 'Storage shed · 1.85m + 3 panels', value: 45000 });
    }
    if (evCharger) rows.push({ label_sv: 'EV-laddare 22 kW', label_en: '22 kW EV charger', value: 14000 });
    if (battery)   rows.push({ label_sv: 'Batterilager 10 kWh', label_en: '10 kWh battery storage', value: 78000 });

    const subtotal = rows.reduce((a, b) => a + b.value, 0);
    const rotavdrag = Math.min(subtotal * 0.5, 50000);
    rows.push({ label_sv: 'Grön ROT (50%, max 50 000 kr)', label_en: 'Green ROT (50%, max 50,000 SEK)', value: -Math.round(rotavdrag), discount: true });
    const total = subtotal - Math.round(rotavdrag);
    return { rows, subtotal, total };
  }, [model, walls, roof, color, evCharger, battery, shed, shedD, lang, m]);

  const monthly = Math.round(breakdown.total / 84);

  const [rotation, setRotation] = useState('side');
  const D_VIEWS = ['hero', 'front', 'aerial', 'rear'];
  // Reset the camera view to the model's default when switching models
  useEffect(() => { setRotation(model === 'd' ? 'hero' : 'side'); }, [model]);
  // For Model D, rotation holds a D view name; fall back to hero if stale.
  const exteriorView = model === 'd'
    ? (D_VIEWS.includes(rotation) ? rotation : 'hero')
    : rotation;

  // --- Scroll-driven section tracking ---
  // When the Tillval section reaches the middle of the viewport, swap the
  // visual to the dedicated charger/battery angle. Scrolling away reverts to
  // whatever rotation (side|top) the user last picked.
  const tillvalRef = useRef(null);
  const shedRef = useRef(null); // kept for potential future use
  const [section, setSection] = useState(null); // null | 'tillval'

  useEffect(() => {
    const el = tillvalRef.current;
    if (!el) return;
    const obs = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          setSection('tillval');
        } else {
          // Only un-set if WE set it — never overwrite a future section name
          setSection(prev => (prev === 'tillval' ? null : prev));
        }
      });
    }, {
      // Tighter band: fires when the section is near the viewport center,
      // not as soon as it enters the upper-mid third.
      rootMargin: '-48% 0px -48% 0px',
      threshold: 0
    });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  // "Sticky" scroll while in the tillval section — halves wheel/trackpad delta
  // so it takes roughly 2x as much scrolling to pass through. Restores normal
  // scroll the moment section flips back to null.
  useEffect(() => {
    if (section !== 'tillval') return;
    const onWheel = (e) => {
      e.preventDefault();
      window.scrollBy({ top: e.deltaY * 0.5, left: 0, behavior: 'instant' });
    };
    window.addEventListener('wheel', onWheel, { passive: false });
    return () => window.removeEventListener('wheel', onWheel);
  }, [section]);

  const placeholderLabel =
    `${m.name} · ${COLORS.find(c => c.id === color)?.[lang === 'en' ? 'name_en' : 'name_sv']} · ${section === 'tillval' ? 'tillval' : rotation}`;

  // Render image path. Three layers, first match wins:
  // 1. Tillval section in view → tillvalImage() lookup (dedicated tillval angle).
  // 2. Outside tillval → heroImage() lookup for configs we have explicit hero
  //    renders for (walled-shed-off currently has all 4 addon combos).
  // 3. Fall back to the canonical modport-<...>-<rotation>.webp filename. May
  //    404 → placeholder for configs we haven't rendered yet.
  const tillvalFile = section === 'tillval'
    ? tillvalImage(model, color, walls, shed, evCharger, battery)
    : null;
  const heroFile = section !== 'tillval'
    ? heroImage(model, color, walls, shed, evCharger, battery, rotation)
    : null;
  // Model D: dedicated d-<color>-<roof>-<walls>-<shed>[-addons]-<view> renders.
  // Guard the roof against the one render-tick where the finish changes before the
  // effect corrects an unrendered combo (e.g. mono→red): use a roof we actually have.
  const safeRoof = roofAvailable(model, color, roof)
    ? roof
    : (ROOFS.find(r => roofAvailable(model, color, r.id)) || {}).id || roof;
  const dFile = (model === 'd')
    ? dImage(color, safeRoof, walls, shedD, evCharger, battery, section === 'tillval' ? 'tillval' : exteriorView)
    : null;
  // Color-specific subfolder under renders/. Each color's renders are organised
  // into "model <m> <colorName>" — e.g. "model s white", "model s red".
  const COLOR_FOLDER = { vit: 'white', sienna: 'red', kalk: 'kalk', anth: 'anth', graf: 'graf', tjar: 'tjar' };
  const colorFolder = `model ${model} ${COLOR_FOLDER[color] || color}`;
  const folder = `renders/${encodeURI(colorFolder)}/`;
  const canonicalPath = `${folder}modport-${model}-${color}-${walls}${shed ? '-shed' : ''}${battery ? '-battery' : ''}${evCharger ? '-charger' : ''}-${rotation}.webp`;
  const renderBase = !m.hasRenders ? null
    : dFile ? folder + encodeURI(dFile)
    : tillvalFile ? folder + encodeURI(tillvalFile)
    : heroFile ? folder + encodeURI(heroFile)
    : canonicalPath;
  // Cache-buster so re-rendered images aren't served stale from browser cache.
  const renderSrc = renderBase ? `${renderBase}?v=${RENDER_VERSION}` : null;
  const [imgFailed, setImgFailed] = useState(false);
  useEffect(() => { setImgFailed(false); }, [renderSrc]);

  return (
    <>
      <div className="config-shell">
        <div className="config-visual">
          <div className="ph visual-ph" data-label={placeholderLabel + ' · render placeholder'}></div>
          {renderSrc && !imgFailed && (
            <CrossfadeImage
              src={renderSrc}
              alt={placeholderLabel}
              className="visual-img"
            />
          )}
          {/* Hidden fallback img keeps onError detection so we can show the placeholder */}
          {renderSrc && (
            <img src={renderSrc} alt="" aria-hidden="true" onError={() => setImgFailed(true)}
                 style={{ position: 'absolute', width: 1, height: 1, opacity: 0, pointerEvents: 'none' }} />
          )}
          <div className="visual-meta">
            <span>{m.name.toUpperCase()}</span>
            <span>{(COLORS.find(c => c.id === color)?.[lang === 'en' ? 'name_en' : 'name_sv'] || '').toUpperCase()}</span>
          </div>
          {section !== 'tillval' && (() => {
            const views = model === 'd' ? D_VIEWS : ['side', 'top'];
            const current = model === 'd' ? exteriorView : rotation;
            const cycle = (delta) => {
              const idx = views.indexOf(current);
              const next = views[((idx === -1 ? 0 : idx) + delta + views.length) % views.length];
              setRotation(next);
            };
            return (
              <>
                {views.length > 1 && (
                  <>
                    <button className="visual-nav prev" onClick={() => cycle(-1)}
                            aria-label={lang === 'en' ? 'Previous angle' : 'Föregående vinkel'}>‹</button>
                    <button className="visual-nav next" onClick={() => cycle(1)}
                            aria-label={lang === 'en' ? 'Next angle' : 'Nästa vinkel'}>›</button>
                  </>
                )}
                <div className="visual-rotate">
                  {views.map(r => (
                    <button key={r}
                            className={current === r ? 'active' : ''}
                            onClick={() => setRotation(r)}>
                      {VIEW_LABELS[r] ? VIEW_LABELS[r][lang === 'en' ? 'en' : 'sv'] : r.toUpperCase()}
                    </button>
                  ))}
                </div>
              </>
            );
          })()}
        </div>

        <div className="config-panel">
          <div className="panel-head">
            <h1>{m.name}</h1>
            <div className="panel-specs">
              {m.specs.map((s, i) => <Spec key={i} s={s} lang={lang} />)}
            </div>
          </div>

          <PaymentTabs pay={pay} setPay={setPay} lang={lang} />

          <ModelSwitcher model={model} setModel={setModel} lang={lang} />

          <ColorChoice value={color} setValue={setColor} lang={lang} model={model} />

          {model === 'd' && <RoofChoice value={roof} setValue={setRoof} lang={lang} model={model} color={color} />}

          <WallChoice value={walls} setValue={setWalls} lang={lang} />

          {model !== 'd' && (
            <div className="section-block" ref={shedRef} data-section="shed">
              <div className="section-label">
                <span>{lang === 'en' ? 'Storage shed' : 'Förråd'}</span>
              </div>
              <ToggleAddon
                active={shed} setActive={setShed}
                title={lang === 'en' ? 'Add storage shed · +3 panels' : 'Lägg till förråd · +3 paneler'}
                desc={lang === 'en' ? '1.85 m enclosed extension at the back. Adds 3 solar panels (+1.6 kWp).' : '1,85 m slutet utrymme bak. Lägger till 3 solpaneler (+1,6 kWp).'}
                price={45000} lang={lang} />
            </div>
          )}

          {model === 'd' && (
            <div className="section-block" ref={shedRef} data-section="shed">
              <div className="section-label">
                <span>{lang === 'en' ? 'Storage shed' : 'Förråd'}</span>
                <span className="val">{shedDLabel(shedD, lang)}</span>
              </div>
              <div className="opt-grid">
                {/* No shed */}
                <button
                  className={`opt ${shedD === 'noshed' ? 'selected' : ''}`}
                  onClick={() => setShedD('noshed')}>
                  <div className="top">
                    <span className="name">{lang === 'en' ? 'No shed' : 'Inget förråd'}</span>
                    <span className="price">{lang === 'en' ? 'Included' : 'Ingår'}</span>
                  </div>
                  <span className="desc">
                    {lang === 'en' ? 'Open carport, no enclosed storage.' : 'Öppen carport utan slutet utrymme.'}
                  </span>
                </button>

                {/* Small shed — expandable into Bak / Sida */}
                <div className={`opt-expand ${shedD === 'smallback' || shedD === 'smallside' ? 'open' : ''}`}>
                  <button
                    className={`opt ${(shedD === 'smallback' || shedD === 'smallside') ? 'selected' : ''}`}
                    onClick={() => {
                      if (shedD === 'smallback' || shedD === 'smallside') setShedD('noshed');
                      else setShedD('smallback');
                    }}>
                    <div className="top">
                      <span className="name">{lang === 'en' ? 'Small shed · ~11 m²' : 'Litet förråd · ~11 m²'}</span>
                      <span className="price">+ {fmt(SHED_PRICE.small, lang)} kr</span>
                    </div>
                    <span className="desc">
                      {lang === 'en'
                        ? 'Compact enclosed box. No extra panels — just somewhere to lock away bikes, tools, tyres.'
                        : 'Kompakt slutet förråd. Inga extra paneler — bara plats att låsa in cyklar, verktyg, däck.'}
                    </span>
                  </button>

                  {(shedD === 'smallback' || shedD === 'smallside') && (
                    <div className="placement-row">
                      <span className="placement-label">{lang === 'en' ? 'Placement' : 'Placering'}</span>
                      <div className="placement-pills">
                        {[
                          { id: 'smallback', sv: 'Bak',  en: 'Back' },
                          { id: 'smallside', sv: 'Sida', en: 'Side' }
                        ].map(p => (
                          <button key={p.id}
                                  className={`placement-pill ${shedD === p.id ? 'active' : ''}`}
                                  onClick={() => setShedD(p.id)}>
                            {lang === 'en' ? p.en : p.sv}
                          </button>
                        ))}
                      </div>
                    </div>
                  )}
                </div>

                {/* Big shed */}
                <button
                  className={`opt ${shedD === 'big' ? 'selected' : ''}`}
                  onClick={() => setShedD('big')}>
                  <div className="top">
                    <span className="name">{lang === 'en' ? 'Large shed · ~19 m² · +12 panels' : 'Stort förråd · ~19 m² · +12 paneler'}</span>
                    <span className="price">+ {fmt(SHED_PRICE.big, lang)} kr</span>
                  </div>
                  <span className="desc">
                    {lang === 'en'
                      ? 'Lengthens the building with a drive-in extension. Roof carries 12 extra panels (+4.8 kWp).'
                      : 'Förlänger byggnaden med en drive-in-tillbyggnad. Taket bär 12 extra paneler (+4,8 kWp).'}
                  </span>
                </button>
              </div>
            </div>
          )}

          <div className="section-block" ref={tillvalRef} data-section="tillval">
            <div className="section-label">
              <span>{lang === 'en' ? 'Add-ons' : 'Tillval'}</span>
            </div>
            <ToggleAddon
              active={evCharger} setActive={setEv}
              title={lang === 'en' ? 'EV charger · 22 kW' : 'EV-laddare · 22 kW'}
              desc={lang === 'en' ? 'Type 2, wall-mounted, integrated cable management.' : 'Typ 2, vägghängd, integrerad kabelhållare.'}
              price={14000} lang={lang} />
            <ToggleAddon
              active={battery} setActive={setBattery}
              title={lang === 'en' ? 'Battery storage · 10 kWh' : 'Batterilager · 10 kWh'}
              desc={lang === 'en' ? 'Store surplus production. Indoor or wall-mount.' : 'Lagra överskott. Inne eller vägghängt.'}
              price={78000} lang={lang} />
          </div>

          <div className="compare">
            <span>{lang === 'en' ? 'View & compare specifications' : 'Visa & jämför specifikationer'}</span>
            <span className="chev">→</span>
          </div>

          <div className="lease-note">
            {lang === 'en'
              ? <>Estimated 84-month financing. <a href="#">Adjust term</a>. Eligible for the green ROT deduction (50%, max 50,000 SEK).</>
              : <>Beräknad finansiering på 84 mån. <a href="#">Justera löptid</a>. Berättigar till grönt ROT-avdrag (50%, max 50 000 kr).</>
            }
          </div>

          <div className="config-recap">
            <div className="recap-head">{lang === 'en' ? 'Your configuration' : 'Din konfiguration'}</div>
            <div className="recap-list">
              <div className="recap-row"><span>{lang === 'en' ? 'Model' : 'Modell'}</span><strong>{m.name} · {lang === 'en' ? m.type_en : m.type_sv}</strong></div>
              {model === 'd' && (
                <div className="recap-row"><span>{lang === 'en' ? 'Roof' : 'Tak'}</span><strong>{(ROOFS.find(r => r.id === roof) || {})[lang === 'en' ? 'name_en' : 'name_sv']}</strong></div>
              )}
              <div className="recap-row"><span>{lang === 'en' ? 'Walls' : 'Väggar'}</span><strong>{(WALLS.find(w => w.id === walls) || {})[lang === 'en' ? 'name_en' : 'name_sv']}</strong></div>
              <div className="recap-row"><span>{lang === 'en' ? 'Finish' : 'Färg'}</span><strong>{(COLORS.find(c => c.id === color) || {})[lang === 'en' ? 'name_en' : 'name_sv']}</strong></div>
              <div className="recap-row"><span>{lang === 'en' ? 'Solar' : 'Sol'}</span><strong>{(m.panels.find(p => p.id === panel) || m.panels[0])[lang === 'en' ? 'name_en' : 'name_sv']}</strong></div>
              {model === 'd' && shedD !== 'noshed' && (
                <div className="recap-row"><span>{lang === 'en' ? 'Storage shed' : 'Förråd'}</span><strong>{shedDLabel(shedD, lang)}</strong></div>
              )}
              {model !== 'd' && shed && (
                <div className="recap-row"><span>{lang === 'en' ? 'Storage shed' : 'Förråd'}</span><strong>{lang === 'en' ? 'Included' : 'Ingår'}</strong></div>
              )}
              {evCharger && (
                <div className="recap-row"><span>{lang === 'en' ? 'EV charger' : 'EV-laddare'}</span><strong>22 kW</strong></div>
              )}
              {battery && (
                <div className="recap-row"><span>{lang === 'en' ? 'Battery storage' : 'Batterilager'}</span><strong>10 kWh</strong></div>
              )}
            </div>
          </div>
        </div>
      </div>

      <div className={`breakdown ${breakdownOpen ? 'open' : ''}`}>
        {breakdown.rows.map((r, i) => (
          <div className="breakdown-row" key={i}>
            <span>{lang === 'en' ? r.label_en : r.label_sv}</span>
            <strong style={r.discount ? { color: 'var(--text-2)' } : null}>
              {r.value < 0 ? '− ' : ''}{fmt(Math.abs(r.value), lang)} kr
            </strong>
          </div>
        ))}
        <div className="breakdown-row total">
          <span>{lang === 'en' ? 'Total after ROT' : 'Totalt efter ROT'}</span>
          <strong>{fmt(breakdown.total, lang)} kr</strong>
        </div>
      </div>

      <div className="config-footer">
        <div className="total">
          <div className="v" onClick={() => setBreakdownOpen(!breakdownOpen)}>
            {pay === 'cash'
              ? <>{fmt(breakdown.total, lang)} kr <span className="chev">{breakdownOpen ? '▾' : '▴'}</span></>
              : <>{fmt(monthly, lang)} kr/mån <span className="chev">{breakdownOpen ? '▾' : '▴'}</span></>
            }
          </div>
          <div className="l">
            {pay === 'cash'
              ? (lang === 'en' ? 'TOTAL INCL. ROT & VAT' : 'TOTALT INKL. ROT & MOMS')
              : (lang === 'en' ? 'EST. 84-MONTH FINANCING' : 'BERÄKNAT 84 MÅN')
            }
          </div>
        </div>
        <a href="#" className="btn btn-primary btn-lg">
          {lang === 'en' ? 'Order now' : 'Beställ nu'}
        </a>
      </div>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />);
