js/new_calc.js.bak_20260428_184207_addon

Code: DEV-FDFCD17E Size: 77.7 KB Lines: 1467 Path: /home/prodconfig.wenesthosting.com/dev.solargroup.wenest.se/js/new_calc.js.bak_20260428_184207_addon

Task / Comment

Open report form
// new_calc.js — Ny Kalkyl startsida (ersätter kalkyl-ny.js)
// Läser från /api/calc_builder.php och visar endast konfiguratorer som har grid_image + grid_order.

var NEW_CALC_VERSION = '1.0.8';
var NEW_CALC_BUILD   = '2026-04-24';
try { window.NEW_CALC_VERSION = NEW_CALC_VERSION; window.NEW_CALC_BUILD = NEW_CALC_BUILD; } catch(e){}
try { console.log('%c[new_calc.js] v' + NEW_CALC_VERSION + ' · ' + NEW_CALC_BUILD, 'color:#024550;font-weight:700'); } catch(e){}

function _newCalcRenderVersionBadge(){
    try {
        // 1. I kategoriväljarens file-strip
        var strip = document.querySelector('#newCalcShell .new-calc-file-strip');
        if(strip && !strip.querySelector('.new-calc-version-badge')){
            var b1 = document.createElement('span');
            b1.className = 'new-calc-version-badge';
            b1.title = 'new_calc.js byggd ' + NEW_CALC_BUILD;
            b1.style.cssText = 'display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border:1px solid #bfe4b8;border-radius:999px;background:#f2fbee;color:#2a6b1f;font-size:11px;font-weight:700;font-family:inherit;margin-left:auto';
            b1.textContent = 'v' + NEW_CALC_VERSION;
            strip.appendChild(b1);
        }
        // 2. Bredvid cfgFileLabel (konfiguratorns header — syns i alla modes)
        var lbl = document.getElementById('cfgFileLabel');
        if(lbl && lbl.parentNode && !document.getElementById('newCalcVersionPill')){
            var b2 = document.createElement('span');
            b2.id = 'newCalcVersionPill';
            b2.title = 'new_calc.js byggd ' + NEW_CALC_BUILD;
            b2.style.cssText = 'margin-left:6px;font-size:10px;color:#2a6b1f;background:#f2fbee;border:1px solid #bfe4b8;padding:3px 8px;border-radius:4px;font-family:monospace;font-weight:700';
            b2.textContent = 'v' + NEW_CALC_VERSION;
            lbl.parentNode.insertBefore(b2, lbl.nextSibling);
        }
    } catch(e){}
}
if(document.readyState === 'loading'){ document.addEventListener('DOMContentLoaded', _newCalcRenderVersionBadge); }
else { _newCalcRenderVersionBadge(); }

var _newCalcActive = false;

function newCalc(kundData){
    _newCalcActive = true;
    // NY KALKYL = nytt sessionID. Alla sub-sessioner + varukorg binds till det.
    // Gamla sessioners data ligger kvar men är OSYNLIGA (fel namespace).
    if(typeof prisStartNewCalc === 'function'){ prisStartNewCalc(); }
    else if(typeof _kalkylItems !== 'undefined') { _kalkylItems = {}; }
    if(typeof _cartItems !== 'undefined' && Array.isArray(_cartItems)) _cartItems.length = 0;
    try { sessionStorage.removeItem('solarCart'); } catch(e){}
    if(typeof currentQuoteId !== 'undefined') currentQuoteId = null;
    if(typeof _cfgDirty !== 'undefined') _cfgDirty = false;
    if(typeof _vpPumps !== 'undefined') _vpPumps = [];
    if(typeof _fkWindows !== 'undefined') _fkWindows = [];
    if(typeof _lbTillval !== 'undefined') _lbTillval = [];
    if(typeof _lbSelected !== 'undefined'){ _lbSelected = null; _lbPrice = 0; }
    if(typeof _vpSelected !== 'undefined'){ _vpSelected = null; _vpPrice = 0; }
    if(typeof _batInitialized !== 'undefined') _batInitialized = false;

    var titleEl = document.getElementById('kalkylTitle');
    if(titleEl) titleEl.textContent = 'Ny Kalkyl';
    var fileLabel = document.getElementById('cfgFileLabel');
    if(fileLabel) fileLabel.textContent = 'new_calc.js';

    // Navigera till konfigurator-sidan
    if(typeof currentPage !== 'undefined') currentPage = 'konfigurator';
    document.querySelectorAll('.page-content').forEach(function(p){ p.classList.remove('active'); });
    var pageEl = document.getElementById('page-konfigurator');
    if(pageEl) pageEl.classList.add('active');
    document.querySelectorAll('.nav-item').forEach(function(n){
        if(n.dataset && n.dataset.page === 'konfigurator') n.classList.add('active');
        else n.classList.remove('active');
    });

    var listView = document.getElementById('kalkylListView');
    if(listView) listView.style.display = 'none';
    var configView = document.getElementById('kalkylConfigView');
    if(configView) configView.style.display = 'block';

    _resetCalcView();

    if(typeof pendingKalkylCustomer !== 'undefined') pendingKalkylCustomer = kundData || null;
    if(typeof populateKalkylFromCustomer === 'function') populateKalkylFromCustomer();

    var photosSection = document.getElementById('kalkylPhotosSection');
    if(photosSection) photosSection.style.display = '';

    _showNewCalcGrid();
    _loadNewCalcCards();

    setTimeout(_resetCalcView, 100);
    setTimeout(_resetCalcView, 500);
}

function _resetCalcView(){
    var cs = document.getElementById('categorySelect');
    if(cs) cs.value = '';

    ['solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView',
     'laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView',
     'isoleringConfigView','summaryProductList'].forEach(function(v){
        var el = document.getElementById(v);
        if(el) el.style.display = 'none';
    });

    var afv = document.getElementById('affarView');
    if(afv) afv.style.display = 'none';
    var pqv = document.getElementById('productQuoteView');
    if(pqv) pqv.remove();

    // Gamla griden ska ALDRIG visas längre — ersätts av new-calc-grid
    var oldGrid = document.getElementById('cfgCategoryGrid');
    if(oldGrid) oldGrid.style.display = 'none';

    if(typeof setKalkylViewMode === 'function') setKalkylViewMode(_newCalcActive ? 'new' : 'config');
    if(_newCalcActive) _showNewCalcGrid();
}

function _showNewCalcGrid(){
    var shell = document.getElementById('newCalcShell');
    if(!shell) return;
    var configView = document.getElementById('kalkylConfigView');
    if(configView && shell.parentElement !== configView){
        configView.appendChild(shell);
    }
    // Städa bort förra render-vyn
    var renderArea = document.getElementById('newCalcRenderArea');
    if(renderArea) renderArea.remove();
    var banner = document.getElementById('newCalcLegacyBanner');
    if(banner) banner.remove();
    // Dölj kalkyl-summary om det är aktivt (NewCalc har sin egen vy)
    var sumView = document.getElementById('summaryProductList');
    if(sumView) sumView.style.display = 'none';
    // Existerande cfgCategoryBar ska synas och peka på CalcBuilder-konfigar
    _ensureSwitchBar('');
    shell.style.display = 'block';
}

function _hideNewCalcGrid(){
    var shell = document.getElementById('newCalcShell');
    if(shell) shell.style.display = 'none';
}

function _loadNewCalcCards(){
    var grid = document.getElementById('newCalcGrid');
    var empty = document.getElementById('newCalcEmpty');
    if(!grid) return;
    grid.innerHTML = '';

    fetch('/api/calc_builder.php')
        .then(function(r){ return r.json(); })
        .then(function(data){
            var items = (data && data.configurators) || [];
            // Filtrera: endast de som har grid_image (gridbild uppladdad) OCH grid_order är satt
            var cards = items.filter(function(c){
                return c.grid_image && c.grid_image !== '' && c.grid_order != null && c.grid_order !== '';
            }).sort(function(a, b){
                return (parseInt(a.grid_order) || 0) - (parseInt(b.grid_order) || 0);
            });

            if(!cards.length){
                grid.style.display = 'none';
                if(empty) empty.style.display = 'block';
                return;
            }

            if(empty) empty.style.display = 'none';
            grid.style.display = 'grid';
            grid.innerHTML = cards.map(_renderNewCalcCard).join('');
        })
        .catch(function(err){
            console.error('new_calc: load error', err);
            grid.style.display = 'none';
            if(empty){
                empty.style.display = 'block';
                var body = empty.querySelector('.new-calc-empty-body');
                if(body) body.textContent = 'Kunde inte läsa konfiguratorer från servern.';
            }
        });
}

function _renderNewCalcCard(cfg){
    var cat = (cfg.category || '').replace(/"/g, '"');
    var title = (cfg.title || cfg.category_label || cfg.category || '').replace(/</g, '&lt;');
    var meta = (cfg.builder_mode === 'advanced' ? 'Avancerad' : (cfg.builder_mode === 'code' ? 'Code' : 'Enkel'));
    var img = cfg.grid_image ? '<img class="new-calc-card-image" src="' + cfg.grid_image.replace(/"/g, '&quot;') + '" alt="">'
                             : '<div class="new-calc-card-image"></div>';
    return '<div class="new-calc-card" onclick="openNewCalcCategory(\'' + cat + '\')">'
        + img
        + '<div class="new-calc-card-body">'
        +   '<div class="new-calc-card-title">' + title + '</div>'
        +   '<div class="new-calc-card-meta">' + meta + '</div>'
        + '</div>'
        + '</div>';
}

// Sub-sessioner ägs av prissammanstallning.js — vi bara använder API:t.
// prisGetSubSession(cat) / prisSaveSubSession(cat, data) / prisCommitSubSession(cat, entry) / prisClearSubSession(cat)

function openNewCalcCategory(categoryId, opts){
    // Byter vi från en pågående kalkyl till annan kategori? Varna då.
    if(_hasUnsavedCalc() && _newCalcContext.categoryId !== categoryId){
        if(!_confirmLeaveCalc()){
            // Återställ kategori-dropdownen till nuvarande värde om användaren backar
            var sel = document.getElementById('categorySelect');
            if(sel) sel.value = _newCalcContext.categoryId || '';
            return;
        }
        // Rensa påbörjad kalkyl innan vi byter
        _newCalcContext.selectedProductId = null;
        _newCalcContext.tillval = [];
        _persistSub();
    }
    _newCalcActive = false;
    _hideNewCalcGrid();
    // Garantera att kalkylConfigView är synlig (kan ha dolts av Calc_summary close-flöde)
    var _cfgView = document.getElementById('kalkylConfigView');
    var _listView = document.getElementById('kalkylListView');
    if(_cfgView) _cfgView.style.display = 'block';
    if(_listView) _listView.style.display = 'none';
    // Ladda/skapa sub-session via prissammanstallning — isolerad per kategori
    _newCalcContext = (typeof prisGetSubSession === 'function')
        ? prisGetSubSession(categoryId)
        : { categoryId: categoryId, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0, editEntryId: null };
    _newCalcContext.categoryId = categoryId;
    // Returnera Promise så edit-flödet kan vänta tills rendering är klar INNAN openForEdit.
    return fetch('/api/calc_builder.php?category=' + encodeURIComponent(categoryId))
        .then(function(r){ return r.json(); })
        .then(function(data){
            var cfg = data && data.success ? data.configurator : null;
            if(cfg && cfg.builder_mode === 'code' && cfg.code){
                _renderCalcBuilderCode(cfg);
                return;
            }
            if(cfg && cfg.builder_mode === 'advanced' && (cfg.blocks || []).length){
                _renderCalcBuilderAdvanced(cfg);
                return;
            }
            // Simple-mode (eller okänt): rendera produktkatalog för kategorin
            _renderCalcBuilderSimple(categoryId, cfg);
        })
        .catch(function(err){
            console.warn('openNewCalcCategory: calc_builder fetch failed', err);
            _fallbackToLegacy(categoryId, null);
        });
}

function _hideAllConfigViews(){
    // OBS: cfgCategoryBar ska INTE döljas — den är vår kalkyl-switch (återanvänds)
    ['solarConfigView','genericConfigView','fonsterConfigView','batteriConfigView',
     'laddboxConfigView','taktvatConfigView','varmepumpConfigView','takConfigView',
     'isoleringConfigView','cfgCategoryGrid','summaryProductList'].forEach(function(id){
        var el = document.getElementById(id);
        if(el) el.style.display = 'none';
    });
    var afv = document.getElementById('affarView');
    if(afv) afv.style.display = 'none';
    var existing = document.getElementById('newCalcRenderArea');
    if(existing) existing.remove();
}

function _ensureRenderArea(){
    var area = document.getElementById('newCalcRenderArea');
    if(area) return area;
    area = document.createElement('div');
    area.id = 'newCalcRenderArea';
    area.style.cssText = 'background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:24px;margin-bottom:16px';
    var configView = document.getElementById('kalkylConfigView');
    if(configView) configView.appendChild(area);
    return area;
}

function _ensureSwitchBar(currentCategoryId){
    // Återanvänd existerande #cfgCategoryBar / #categorySelect (från konfigurator.php)
    var bar = document.getElementById('cfgCategoryBar');
    var sel = document.getElementById('categorySelect');
    if(bar) bar.style.display = '';
    if(!sel) return;
    // Routa om till NewCalc istället för gamla changeCategory
    sel.onchange = function(){ if(this.value) openNewCalcCategory(this.value); };
    fetch('/api/calc_builder.php')
        .then(function(r){ return r.json(); })
        .then(function(data){
            var items = (data && data.configurators) || [];
            sel.innerHTML = '<option value="">— välj —</option>'
                + items.map(function(c){
                    var lbl = (c.title || c.category_label || c.category);
                    var selAttr = c.category === currentCategoryId ? ' selected' : '';
                    return '<option value="' + _esc(c.category) + '"' + selAttr + '>' + _esc(lbl) + '</option>';
                }).join('');
        }).catch(function(){});
}

function _setCalcFileLabel(text){
    var lbl = document.getElementById('cfgFileLabel');
    if(lbl) lbl.textContent = text;
    _newCalcRenderVersionBadge();
}

function _calcBuilderHeader(cfg, source){
    return '';
}

// Receptet: läs Layout-config från cfg och bygg ALLTID samma struktur (head + content + sidebar).
// Gäller alla CalcBuilder-modes (simple/advanced/code) — sidebaren placeras enligt cfg.sidebar_position.
function _calcLayoutSpec(cfg){
    var pos = String((cfg && cfg.sidebar_position) || 'right').toLowerCase();
    if(pos === 'vänster' || pos === 'vanster') pos = 'left';
    if(pos === 'höger' || pos === 'hoger') pos = 'right';
    if(pos === 'ingen') pos = 'none';
    var mod = (cfg && cfg.sidebar_module) || 'price-summary';
    var hasSidebar = pos !== 'none' && mod && mod !== 'none';
    return { sidebarPos: pos, sidebarMod: mod, hasSidebar: hasSidebar };
}

function _calcBuildLayout(cfg, contentHtml, modeName){
    var spec = _calcLayoutSpec(cfg);
    var sw = (cfg && cfg.sidebar_width) ? String(cfg.sidebar_width).trim() : '320px';
    if(!/(px|%|rem|em|vw)$/.test(sw)) sw = sw + 'px';
    var gridCols = spec.hasSidebar
        ? (spec.sidebarPos === 'left' ? sw + ' minmax(0,1fr)' : 'minmax(0,1fr) ' + sw)
        : 'minmax(0,1fr)';
    // Smartfilter-host placeras AV simple/snippet — INTE här (för att ge varje mode kontroll över position).
    var contentCol = '<div id="newCalcContent" style="min-width:0">' + contentHtml + '</div>';
    var sidebarCol = spec.hasSidebar
        ? '<aside id="newCalcSidebarCol" style="min-width:0"><div id="newCalcPrisSidebar"></div></aside>'
        : '';
    return _calcBuilderHeader(cfg, modeName)
        + '<div class="nc-layout-grid" style="display:grid;grid-template-columns:' + gridCols + ';gap:12px;align-items:start">'
        + (spec.sidebarPos === 'left' ? sidebarCol + contentCol : contentCol + sidebarCol)
        + '</div>';
}

// Renderar smartfilter-baren efter layout är i DOM. Anropas från alla mode-renderfunktioner.
function _smartFilterPostRender(cfg){
    if (!cfg || !(cfg.smart_filter == 1 || cfg.smart_filter === true || cfg.smart_filter === '1')) return;
    if (typeof _renderSmartFilterBar === 'function') {
        try { _renderSmartFilterBar(cfg.category, []); } catch(e){ console.warn('smartFilter render', e); }
    }
}

function _renderCalcBuilderCode(cfg){
    _hideAllConfigViews();
    _setCalcFileLabel('CalcBuilder/code · ' + (cfg.title || cfg.category));
    _newCalcContext = (typeof prisGetSubSession === 'function')
        ? prisGetSubSession(cfg.category) : _newCalcContext;
    _newCalcContext.categoryId = cfg.category;
    _newCalcContext.cfg = cfg;
    var html = String(cfg.code || '').replace(/\{\{\s*(\w+)\s*\}\}/g, function(m, k){
        var vars = { category: cfg.category || '', title: cfg.title || '', version: cfg.version || '1.0.0' };
        return vars[k] != null ? vars[k] : m;
    });
    var area = _ensureRenderArea();
    var content = '<div class="calc-builder-code-content">' + html + '</div>';
    area.innerHTML = _calcBuildLayout(cfg, content, 'code-mode');
    area.style.display = 'block';
    var spec = _calcLayoutSpec(cfg);
    if(spec.hasSidebar) _renderNewCalcSidebar();
    _smartFilterPostRender(cfg);
    // Execute <script>-taggar i cfg.code (innerHTML kör inte scripts av sig själv).
    // Tillåter snippets som WindowSnippet.mount(...) att starta sig själva.
    var contentHost = area.querySelector('.calc-builder-code-content');
    if(contentHost){
        var scripts = contentHost.querySelectorAll('script');
        scripts.forEach(function(oldScript){
            var s = document.createElement('script');
            if(oldScript.src){ s.src = oldScript.src; }
            else { s.textContent = oldScript.textContent; }
            oldScript.parentNode.replaceChild(s, oldScript);
        });
    }
}

function _renderCalcBuilderAdvanced(cfg){
    _hideAllConfigViews();
    _setCalcFileLabel('CalcBuilder/advanced · ' + (cfg.title || cfg.category));
    _newCalcContext = (typeof prisGetSubSession === 'function')
        ? prisGetSubSession(cfg.category) : _newCalcContext;
    _newCalcContext.categoryId = cfg.category;
    _newCalcContext.cfg = cfg;
    var area = _ensureRenderArea();
    var blocksHtml = '';
    if(window.CalcBlocks && typeof window.CalcBlocks.render === 'function'){
        blocksHtml = window.CalcBlocks.render(cfg.blocks || [], {});
    } else {
        blocksHtml = '<div style="padding:12px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px">CalcBlocks renderer saknas — kan inte rendera advanced-blocken.</div>';
    }
    area.innerHTML = _calcBuildLayout(cfg, blocksHtml, 'advanced-mode');
    area.style.display = 'block';
    var spec = _calcLayoutSpec(cfg);
    if(spec.hasSidebar) _renderNewCalcSidebar();
    _smartFilterPostRender(cfg);
}

var _newCalcContext = { categoryId: '', cfg: null, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0 };

function _renderCalcBuilderSimple(categoryId, cfg){
    _hideAllConfigViews();
    _setCalcFileLabel('CalcBuilder/simple · ' + categoryId);
    _ensureSwitchBar(categoryId);
    // Använd sub-session (namespaced per kategori) — uppdatera cfg men behåll state
    _newCalcContext = (typeof prisGetSubSession === 'function')
        ? prisGetSubSession(categoryId)
        : _newCalcContext;
    _newCalcContext.categoryId = categoryId;
    _newCalcContext.cfg = cfg;

    var area = _ensureRenderArea();
    var title = (cfg && (cfg.title || cfg.category_label)) || categoryId;
    var spec = _calcLayoutSpec(cfg);

    var _sid = (typeof prisGetCurrentSessionId === 'function') ? prisGetCurrentSessionId() : '(saknas)';
    var _subKey = _sid + '_' + categoryId;
    var _sessInfo = '<div class="calc-session-pills" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;font-family:monospace;font-size:10px">'
        + '<span style="padding:3px 8px;border:1px solid #bfdbfe;background:#eff6ff;color:#1e40af;border-radius:6px">SESSION: ' + _esc(_sid) + '</span>'
        + '<span style="padding:3px 8px;border:1px solid #bbf7d0;background:#f0fdf4;color:#166534;border-radius:6px">SUB: ' + _esc(_subKey) + '</span>'
        + '</div>';
    var smartFilterEnabled = !!(cfg && (cfg.smart_filter == 1 || cfg.smart_filter === true || cfg.smart_filter === '1'));
    var smartFilterHostHtml = smartFilterEnabled
        ? '<div id="newCalcSmartFilter" data-cat="' + _esc(categoryId) + '" style="margin:0 0 14px"></div>'
        : '';
    // Pris-toggle i rubriken (default dold). window._simpleShowPrices[cat] = true/false.
    if(!window._simpleShowPrices) window._simpleShowPrices = {};
    var _showP = !!window._simpleShowPrices[categoryId];
    var _pricesAllowed = (typeof getRoleSetting !== 'function') || getRoleSetting('kalkyler.show_prices') === '1';
    var _eyeOn  = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/></svg>';
    var _eyeOff = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
    var _priceToggleBtn = _pricesAllowed
        ? '<button type="button" onclick="toggleSimplePrices(\'' + _esc(categoryId) + '\')" title="' + (_showP ? 'Dölj priser' : 'Visa priser') + '" style="background:none;border:1px solid #e5e7eb;border-radius:6px;padding:4px 8px;cursor:pointer;color:' + (_showP ? '#024550' : '#64748b') + ';font-size:12px;vertical-align:middle;margin-left:8px">' + (_showP ? _eyeOn : _eyeOff) + '</button>'
        : '';
    if(!_pricesAllowed) _showP = false;
    var simpleContent = _sessInfo
        + '<div style="display:flex;align-items:center;margin:0 0 4px"><h2 style="font-size:18px;font-weight:700;color:#1a1a1a;margin:0;flex:1">' + _esc(title) + '</h2>' + _priceToggleBtn + '</div>'
        + '<p style="font-size:13px;color:#64748b;margin:0 0 18px">Välj en produkt för att lägga till i kalkylen.</p>'
        + smartFilterHostHtml
        + '<div id="newCalcSimpleGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px"></div>'
        + '<div id="newCalcProductDetail" style="display:none"></div>';

    area.innerHTML = _calcBuildLayout(cfg || { builder_mode:'simple', version:'1.0.0', sidebar_module:'price-summary', sidebar_position:'right', category: categoryId, smart_filter: smartFilterEnabled ? 1 : 0 }, simpleContent, 'simple-mode');
    area.style.display = 'block';

    if(spec.hasSidebar) _renderNewCalcSidebar();

    function paint(){
        var grid = document.getElementById('newCalcSimpleGrid');
        if(!grid) return;
        var src = (typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)) ? catalogProducts : [];
        var products = src
            .filter(function(p){ return p && p.cat === categoryId; })
            .sort(function(a,b){ return (a.sort_order||0)-(b.sort_order||0) || String(a.name||'').localeCompare(String(b.name||''),'sv'); });
        // Smartfilter: applicera aktivt filter (per säljare)
        if(smartFilterEnabled){
            _renderSmartFilterBar(categoryId, products);
            var f = _smartFilterActive(categoryId);
            if(f){
                if(Array.isArray(f.products) && f.products.length){
                    products = products.filter(function(p){ return f.products.indexOf(p.id) !== -1; });
                }
                if(Array.isArray(f.models) && f.models.length){
                    products = products.filter(function(p){
                        var pm = _smartFilterModelLabels(p);
                        return pm.some(function(m){ return f.models.indexOf(m) !== -1; });
                    });
                }
            }
        }
        if(!products.length){
            grid.innerHTML = '<div style="grid-column:1/-1;padding:18px;background:#fef3c7;border:1px solid #f59e0b;border-radius:10px;color:#92400e;font-size:13px">Inga produkter i kategorin <strong>' + _esc(categoryId) + '</strong>. Lägg till produkter via Produktkatalog.</div>';
            return;
        }
        var showPrices = !!(window._simpleShowPrices && window._simpleShowPrices[categoryId]);
        grid.innerHTML = products.map(function(p){
            var img = p.img ? '<img src="' + _esc(p.img) + '" style="width:100%;height:130px;object-fit:cover;border-radius:8px;background:#f8fafc">'
                : '<div style="width:100%;height:130px;border-radius:8px;background:#f8fafc;display:flex;align-items:center;justify-content:center;color:#cbd5e1;font-size:11px">Ingen bild</div>';
            var price = (p.price != null) ? Number(p.price).toLocaleString('sv-SE') + ' kr' : '';
            return '<div onclick="newCalcOpenProduct(\'' + _esc(p.id) + '\')" style="cursor:pointer;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:10px;transition:all .15s" onmouseover="this.style.borderColor=\'#024550\';this.style.transform=\'translateY(-2px)\'" onmouseout="this.style.borderColor=\'#e5e7eb\';this.style.transform=\'translateY(0)\'">'
                + img
                + '<div style="margin-top:8px;font-size:13px;font-weight:700;color:#1a1a1a">' + _esc(p.name||'') + '</div>'
                + (showPrices && price ? '<div style="margin-top:4px;font-size:13px;font-weight:700;color:#024550">' + price + '</div>' : '')
                + '</div>';
        }).join('');
    }

    window.toggleSimplePrices = function(cat){
        if(!window._simpleShowPrices) window._simpleShowPrices = {};
        window._simpleShowPrices[cat] = !window._simpleShowPrices[cat];
        // Re-render kategorin
        if(typeof openNewCalcCategory === 'function') openNewCalcCategory(cat);
    };

    var hasCatalog = typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts) && catalogProducts.length;
    if(!hasCatalog && typeof loadCatalogFromDB === 'function'){
        Promise.resolve(loadCatalogFromDB()).then(paint).catch(paint);
    } else {
        paint();
    }
    // Exponera repaint-funktion så smartfilter kan applicera utan att reboota hela konfiguratorn.
    window._newCalcRepaintSimple = function(cat){ if(cat === categoryId) paint(); };
}

function _esc(v){
    return String(v == null ? '' : v).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

// === Inline produktvy + tillval ===

function newCalcOpenProduct(productId, entryId){
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===productId;}) : null;
    if(!p) return;
    var editEntry = null;
    if(entryId && typeof _kalkylItems !== 'undefined'){
        var item = _kalkylItems[_newCalcContext.categoryId];
        var entries = item ? (Array.isArray(item.entries) ? item.entries : [item]) : [];
        editEntry = entries.find(function(e){ return e.entry_id === entryId; }) || null;
    }
    _newCalcContext.selectedProductId = productId;
    _newCalcContext.productQty = (editEntry && editEntry.qty) ? parseFloat(editEntry.qty) : 1;
    _newCalcContext.selectedVariantIdx = (editEntry && editEntry.variant_idx != null) ? parseInt(editEntry.variant_idx) : 0;
    _newCalcContext.editEntryId = entryId || null;
    _loadInlineTillval(productId).then(function(){
        if(editEntry && Array.isArray(editEntry.tillval)){
            var savedById = {};
            editEntry.tillval.forEach(function(tv){ if(tv && tv.id != null) savedById[tv.id] = tv; });
            _newCalcContext.tillval.forEach(function(tv){
                var saved = savedById[tv.id];
                if(saved){ tv.checked = true; if(saved.qty != null) tv.userQty = parseFloat(saved.qty) || tv.userQty; }
                else if(!tv.included){ tv.checked = false; }
            });
        }
        _persistSub();
        _renderInlineProduct(p);
        _renderNewCalcSidebar();
    });
}

function _hasUnsavedCalc(){
    return !!(_newCalcContext && _newCalcContext.selectedProductId);
}

function _confirmLeaveCalc(){
    if(!_hasUnsavedCalc()) return true;
    var productName = '';
    if(typeof catalogProducts !== 'undefined' && catalogProducts.find){
        var p = catalogProducts.find(function(x){ return x.id === _newCalcContext.selectedProductId; });
        if(p) productName = p.name || '';
    }
    var subject = productName || 'denna produkt';
    return confirm('Du har en påbörjad kalkyl på ' + subject + '.\n\nOm du går härifrån utan att klicka "Lägg till i kalkyl" förlorar du dina val.\n\nVill du fortsätta ändå?');
}

function newCalcBackToGrid(force){
    if(!force && !_confirmLeaveCalc()) return;
    _newCalcContext.selectedProductId = null;
    _newCalcContext.tillval = [];
    _persistSub();
    var grid = document.getElementById('newCalcSimpleGrid');
    var detail = document.getElementById('newCalcProductDetail');
    if(grid) grid.style.display = 'grid';
    if(detail){ detail.style.display = 'none'; detail.innerHTML = ''; }
    _renderNewCalcSidebar();
}

function _loadInlineTillval(productId){
    return Promise.all([
        fetch('/api/products.php?cat=tjanster&active=1').then(function(r){return r.json();}),
        fetch('/api/products.php?cat=tillbehor&active=1').then(function(r){return r.json();}),
        fetch('/api/products.php?services_for=' + encodeURIComponent(productId)).then(function(r){return r.json();})
    ]).then(function(results){
        var svcData = results[0], accData = results[1], linkedData = results[2];
        var linkedMap = {};
        if(linkedData && linkedData.success){
            (linkedData.services || []).forEach(function(s){
                linkedMap[s.service_id] = {
                    default_on: parseInt(s.default_on || 0),
                    hidden: parseInt(s.hidden || 0),
                    mandatory: parseInt(s.mandatory || 0),
                    default_qty: s.default_qty != null ? parseFloat(s.default_qty) : null,
                    min_qty: s.min_qty != null ? parseFloat(s.min_qty) : null,
                    max_qty: s.max_qty != null ? parseFloat(s.max_qty) : null
                };
            });
        }
        var svcProducts = (svcData && svcData.success ? (svcData.products || []) : []).map(function(p){ p._is_service = true; return p; });
        var accProducts = (accData && accData.success ? (accData.products || []) : []).map(function(p){ p._is_service = false; return p; });
        var allProducts = [].concat(svcProducts, accProducts);
        _newCalcContext.tillval = allProducts.filter(function(s){ return linkedMap.hasOwnProperty(s.id); }).map(function(s){
            var lnk = linkedMap[s.id] || {};
            var seedQty = lnk.default_qty != null ? lnk.default_qty : 0;
            return {
                id: s.id, name: s.name, price: parseFloat(s.price) || 0, unit: s.unit || 'st',
                checked: !!(lnk.default_on || lnk.mandatory),
                included: !!lnk.mandatory,
                hidden: !!lnk.hidden,
                is_service: !!s._is_service,
                userQty: seedQty,
                linkMin: lnk.min_qty, linkMax: lnk.max_qty, linkDefaultQty: lnk.default_qty,
                rotEligible: parseInt(s.rot_eligible) === 1,
                greenTechEligible: parseInt(s.green_tech_eligible) === 1,
                taxType: s.tax_type || 'NONE'
            };
        });
    }).catch(function(err){ console.warn('newCalc tillval load failed', err); _newCalcContext.tillval = []; });
}

function _getInlineEffectiveTillvalQty(tv){
    var stHasUserQty = (tv.unit === 'st' || !tv.unit) && (tv.linkMax > 1 || tv.linkDefaultQty != null) && tv.userQty > 0;
    if(tv.unit && tv.unit !== 'st' && tv.userQty > 0) return tv.userQty;
    if(stHasUserQty) return tv.userQty;
    return 1;
}

function _getVariantInfo(p){
    var variants = (p.specs && p.specs.variants) || [];
    if(!variants.length) return { hasVariants: false, variants: [], labelKey: '', labelSuffix: '' };
    var first = variants[0];
    var labelKey = '', labelSuffix = '';
    if(first.kwh !== undefined) { labelKey = 'kwh'; labelSuffix = ' kWh'; }
    else if(first.paneler !== undefined) { labelKey = 'paneler'; labelSuffix = ' paneler'; }
    else if(first.cm !== undefined) { labelKey = 'cm'; labelSuffix = ' cm'; }
    else if(first.kvm !== undefined) { labelKey = 'kvm'; labelSuffix = ' m²'; }
    else if(first.material !== undefined) { labelKey = 'material'; labelSuffix = ''; }
    else { var keys = Object.keys(first).filter(function(k){return ['totalt','price','att_betala','gron_teknik','pris_per_m2','sek_pris','article_id','width_mm','length_mm'].indexOf(k) === -1;}); labelKey = keys[0] || ''; labelSuffix = ''; }
    return { hasVariants: true, variants: variants, labelKey: labelKey, labelSuffix: labelSuffix };
}

function _getVariantPrice(p, variant){
    if(!variant) return parseFloat(p.price) || 0;
    if(variant.totalt !== undefined) return parseFloat(variant.totalt) || 0;
    if(variant.att_betala !== undefined && variant.gron_teknik !== undefined) return (parseFloat(variant.att_betala) || 0) + (parseFloat(variant.gron_teknik) || 0);
    if(variant.price !== undefined) return parseFloat(variant.price) || 0;
    if(variant.pris_per_m2 !== undefined) return parseFloat(variant.pris_per_m2) || 0;
    return parseFloat(p.price) || 0;
}

function _getVariantDeduction(p, variant){
    if(variant && variant.gron_teknik !== undefined) return parseFloat(variant.gron_teknik) || 0;
    return 0;
}

// Härleder tax_type från produkt: explicit p.taxType → rot_eligible → green_tech_eligible.
function _deriveTaxType(p){
    if(!p) return 'NONE';
    if(p.taxType && p.taxType !== 'NONE') return p.taxType;
    if(p.rotEligible) return 'ROT';
    if(p.greenTechEligible) return 'GT20';
    return 'NONE';
}

function _taxRate(taxType){
    var tax = (typeof taxSettings !== 'undefined') ? taxSettings : { rot:30, gt20:20, gt50:50 };
    if(taxType === 'ROT') return (tax.rot || 30)/100;
    if(taxType === 'GT20') return (tax.gt20 || 20)/100;
    if(taxType === 'GT50') return (tax.gt50 || 50)/100;
    return 0;
}

function _calcInlineTotal(p){
    var vinfo = _getVariantInfo(p);
    var selectedVariant = vinfo.hasVariants ? vinfo.variants[_newCalcContext.selectedVariantIdx] || vinfo.variants[0] : null;
    var basePrice = vinfo.hasVariants ? _getVariantPrice(p, selectedVariant) : (parseFloat(p.price) || 0);
    var variantDed = vinfo.hasVariants ? _getVariantDeduction(p, selectedVariant) : 0;
    var qty = parseInt(_newCalcContext.productQty) || 1;
    var subtotal = basePrice * qty;
    var tillvalTotal = 0;
    var rotBase = 0;       // underlag för ROT-avdrag
    var gtBase = 0;        // underlag för Grönt teknik-avdrag
    var primaryTax = _deriveTaxType(p);
    if(primaryTax === 'ROT') rotBase += subtotal;
    else if(primaryTax === 'GT20' || primaryTax === 'GT50') gtBase += subtotal;

    _newCalcContext.tillval.forEach(function(tv){
        // Dolda tillval räknas om de är checked (oftast via default_on/mandatory).
        if(!tv.checked) return;
        var eq = _getInlineEffectiveTillvalQty(tv);
        var tvSum = (tv.price || 0) * eq;
        tillvalTotal += tvSum;
        // Härled per tillval — rot_eligible/green_tech_eligible kommer från produktens fält
        if(tv.rotEligible || tv.rot_eligible || tv.taxType === 'ROT') rotBase += tvSum;
        else if(tv.greenTechEligible || tv.green_tech_eligible || tv.taxType === 'GT20' || tv.taxType === 'GT50') gtBase += tvSum;
    });

    // Räkna potentiellt avdrag.
    var rotDed = Math.round(rotBase * _taxRate('ROT'));
    var gtDed  = Math.round(gtBase * (primaryTax === 'GT50' ? _taxRate('GT50') : _taxRate('GT20')));

    // Variant-deduktion (gron_teknik på fönster-variant) — override om satt.
    if(variantDed > 0 && primaryTax === 'NONE'){ primaryTax = 'GT20'; }
    var deductionTotal = variantDed > 0 ? (variantDed * qty) : Math.max(rotDed, gtDed);

    // Om huvudprodukten är NONE men tillval ger avdrag → uppgradera taxType.
    if(primaryTax === 'NONE'){
        if(rotBase > 0) primaryTax = 'ROT';
        else if(gtBase > 0) primaryTax = 'GT20';
    }

    var gross = subtotal + tillvalTotal;
    return { basePrice: basePrice, qty: qty, subtotal: subtotal, tillvalTotal: tillvalTotal, deduction: deductionTotal, total: gross, gross: gross, taxType: primaryTax, variant: selectedVariant };
}

function _renderInlineProduct(p){
    var grid = document.getElementById('newCalcSimpleGrid');
    var detail = document.getElementById('newCalcProductDetail');
    if(!detail) return;
    if(grid) grid.style.display = 'none';
    detail.style.display = 'block';

    var img = p.img ? '<img src="' + _esc(p.img) + '" style="width:100%;border-radius:10px;object-fit:cover;max-height:320px;background:#f8fafc">'
        : '<div style="width:100%;height:240px;border-radius:10px;background:#f8fafc;display:flex;align-items:center;justify-content:center;color:#cbd5e1">Ingen bild</div>';

    var unit = p.unit || 'st';
    var totals = _calcInlineTotal(p);
    var vinfo = _getVariantInfo(p);

    var taxBadge = '';
    if(p.taxType === 'GT20' || p.taxType === 'GT50' || p.greenTechEligible) {
        taxBadge = '<div style="display:inline-flex;align-items:center;gap:6px;background:#10b981;color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;margin-bottom:10px">✓ Grönt teknik-avdrag</div>';
    } else if(p.taxType === 'ROT' || p.rotEligible) {
        taxBadge = '<div style="display:inline-flex;align-items:center;gap:6px;background:#3b82f6;color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;margin-bottom:10px">✓ ROT-avdrag</div>';
    }

    var variantHtml = '';
    if(vinfo.hasVariants && vinfo.variants.length > 1){
        variantHtml = '<div style="margin:14px 0">'
            + '<div style="font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Välj variant</div>'
            + '<div style="display:flex;flex-wrap:wrap;gap:8px">'
            + vinfo.variants.map(function(v, vi){
                var isSel = vi === _newCalcContext.selectedVariantIdx;
                var lbl = (v[vinfo.labelKey] != null ? v[vinfo.labelKey] : '') + vinfo.labelSuffix;
                return '<button onclick="newCalcSetVariant(' + vi + ')" style="padding:10px 18px;border-radius:8px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit;border:1.5px solid ' + (isSel ? '#024550' : '#e5e7eb') + ';background:' + (isSel ? '#024550' : '#fff') + ';color:' + (isSel ? '#fff' : '#334155') + '">' + _esc(lbl) + '</button>';
            }).join('')
            + '</div></div>';
    }

    var isInfoUnit = (unit === 'info');
    var qtyHtml = '';
    if(!isInfoUnit && (!vinfo.hasVariants || (vinfo.labelKey !== 'kwh' && vinfo.labelKey !== 'paneler' && vinfo.labelKey !== 'kvm'))){
        qtyHtml = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">'
            + '<label style="font-size:12px;font-weight:600;color:#64748b">Antal ' + _esc(unit) + '</label>'
            + '<input id="newCalcProductQty" type="number" min="1" step="1" value="' + totals.qty + '" oninput="newCalcSetProductQty(this.value)" style="width:90px;padding:8px 10px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;text-align:center">'
            + '</div>';
    }

    detail.innerHTML = '<button onclick="newCalcBackToGrid()" style="display:inline-flex;align-items:center;gap:6px;background:none;border:none;cursor:pointer;color:#64748b;font-size:13px;font-weight:600;padding:0 0 12px;font-family:inherit">'
        + '<svg viewBox="0 0 24 24" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:2"><polyline points="15 18 9 12 15 6"/></svg>Tillbaka till valet</button>'
        + '<div class="nc-product-detail" style="display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:18px">'
        +   '<div>' + img + '</div>'
        +   '<div>'
        +     '<div style="font-size:11px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px">' + _esc(p.catLabel || p.cat || '') + '</div>'
        +     '<h3 style="font-size:22px;font-weight:700;color:#1a1a1a;margin:4px 0 8px">' + _esc(p.name || '') + '</h3>'
        +     (isInfoUnit ? '' : taxBadge)
        +     (p.desc ? '<p style="font-size:13px;color:#64748b;margin:0 0 14px">' + _esc(p.desc) + '</p>' : '')
        +     variantHtml
        +     qtyHtml
        +     (isInfoUnit ? '' : '<div id="newCalcPriceBox" style="background:#f8fafc;border-radius:10px;padding:14px"></div>')
        +     '<button onclick="newCalcAddToKalkyl()" style="margin-top:14px;width:100%;padding:12px 16px;background:linear-gradient(135deg,' + (_newCalcContext.editEntryId ? '#024550,#035e6b' : '#059669,#10b981') + ');color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;font-family:inherit">' + (_newCalcContext.editEntryId ? 'Uppdatera kalkyl' : 'Lägg till i kalkyl') + '</button>'
        +   '</div>'
        + '</div>'
        + '<div id="newCalcInlineTillvalArea"></div>';

    if(!isInfoUnit) _renderInlinePriceBox(p);
    _renderInlineTillval(p);
}

function _persistSub(){
    if(typeof prisSaveSubSession === 'function' && _newCalcContext && _newCalcContext.categoryId){
        prisSaveSubSession(_newCalcContext.categoryId, _newCalcContext);
    }
}

function newCalcSetVariant(vi){
    _newCalcContext.selectedVariantIdx = parseInt(vi) || 0;
    _persistSub();
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
    if(p) _renderInlineProduct(p);
}

function newCalcSetProductQty(val){
    var n = parseInt(val) || 1;
    if(n < 1) n = 1;
    _newCalcContext.productQty = n;
    _persistSub();
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
    if(p) _renderInlinePriceBox(p);
}

function _renderInlinePriceBox(p){
    var box = document.getElementById('newCalcPriceBox');
    if(!box) return;
    var t = _calcInlineTotal(p);
    var taxLabel = t.taxType === 'GT50' ? 'Skattereduktion (GT50)'
        : t.taxType === 'GT20' ? 'Grönt teknik-avdrag (GT20)'
        : t.taxType === 'ROT' ? 'ROT-avdrag'
        : 'Skatteavdrag';
    box.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:13px;color:#334155;margin-bottom:6px"><span>' + _esc(p.name) + (t.qty > 1 ? ' × ' + t.qty : '') + '</span><span>' + t.subtotal.toLocaleString('sv-SE') + ' kr</span></div>'
        + (t.tillvalTotal > 0 ? '<div style="display:flex;justify-content:space-between;font-size:13px;color:#334155;margin-bottom:6px"><span>Tillval</span><span>+' + t.tillvalTotal.toLocaleString('sv-SE') + ' kr</span></div>' : '')
        + (t.deduction > 0 ? '<div style="display:flex;justify-content:space-between;font-size:12px;color:#94a3b8;margin-bottom:6px;font-style:italic"><span>Möjligt ' + taxLabel + ': -' + t.deduction.toLocaleString('sv-SE') + ' kr</span><span>(regleras i prissammanställningen)</span></div>' : '')
        + '<div style="display:flex;justify-content:space-between;align-items:baseline;border-top:1px solid #e5e7eb;padding-top:8px;margin-top:6px"><span style="font-size:14px;font-weight:700;color:#1a1a1a">Att betala</span><span style="font-size:22px;font-weight:800;color:#024550">' + t.gross.toLocaleString('sv-SE') + ' kr</span></div>';
}

function _renderInlineTillval(p){
    var area = document.getElementById('newCalcInlineTillvalArea');
    if(!area) return;
    // Dold = aldrig synlig i UI (gäller både tjänster och tillbehör). Dolda tillval räknas
    // ändå in i priset/ROT om de är checked (default_on eller mandatory).
    var visible = _newCalcContext.tillval.filter(function(tv){ return !tv.hidden; });
    if(!visible.length){ area.innerHTML = ''; return; }
    area.innerHTML = '<div style="font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px">Tillval</div>'
        + '<div class="nc-tillval-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:8px">'
        + visible.map(function(tv){
            var idx = _newCalcContext.tillval.indexOf(tv);
            var unitText = tv.unit || 'st';
            var unitLabel = '';
            var eq = _getInlineEffectiveTillvalQty(tv);
            var lineTotal = (tv.price || 0) * eq;
            var qtyHtml = '';
            if(tv.checked && tv.unit && tv.unit !== 'st'){
                qtyHtml = '<div style="margin-top:6px;display:flex;align-items:center;gap:6px" onclick="event.preventDefault();event.stopPropagation()">'
                    + '<label style="font-size:11px;color:#64748b">Antal</label>'
                    + '<input type="number" min="0" step="0.1" value="' + (tv.userQty || '') + '" oninput="newCalcSetTillvalQty(' + idx + ',this.value)" style="width:80px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;text-align:center;font-family:inherit">'
                    + '<span style="font-size:11px;color:#64748b">' + _esc(tv.unit) + '</span></div>';
            } else if(tv.checked && (tv.linkMax > 1 || tv.linkDefaultQty != null)){
                var minA = tv.linkMin != null ? tv.linkMin : 1;
                var maxA = tv.linkMax != null ? ' max="' + tv.linkMax + '"' : '';
                var v = tv.userQty > 0 ? tv.userQty : (tv.linkDefaultQty != null ? tv.linkDefaultQty : 1);
                qtyHtml = '<div style="margin-top:6px;display:flex;align-items:center;gap:6px" onclick="event.preventDefault();event.stopPropagation()">'
                    + '<label style="font-size:11px;color:#64748b">Antal</label>'
                    + '<input type="number" min="' + minA + '"' + maxA + ' step="1" value="' + v + '" oninput="newCalcSetTillvalQty(' + idx + ',this.value)" style="width:80px;padding:5px 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;text-align:center;font-family:inherit">'
                    + '<span style="font-size:11px;color:#94a3b8">st' + (tv.linkMax != null ? ' (max ' + tv.linkMax + ')' : '') + '</span></div>';
            }
            var markerHtml = tv.included
                ? '<span title="Obligatorisk" style="width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;background:#059669;color:#fff;border-radius:5px;flex-shrink:0;font-size:11px;font-weight:800">✓</span>'
                : '<input type="checkbox" ' + (tv.checked ? 'checked' : '') + ' style="width:18px;height:18px;accent-color:#059669;flex-shrink:0;pointer-events:none">';
            var mandatoryBadge = tv.included ? ' <span title="Obligatorisk" style="color:#dc2626;font-weight:800;margin-left:4px">*</span>' : '';
            return '<div style="padding:10px 14px;background:' + (tv.checked ? '#f0fdf4' : '#f8f9fa') + ';border:1px solid ' + (tv.checked ? '#bbf7d0' : '#e5e7eb') + ';border-radius:10px">'
                + '<div style="display:flex;align-items:center;gap:8px;cursor:' + (tv.included ? 'default' : 'pointer') + '" onclick="' + (tv.included ? '' : 'newCalcToggleTillval(' + idx + ')') + '">'
                + markerHtml
                + '<span style="flex:1;font-size:13px;font-weight:600;color:#334155">' + _esc(tv.name) + unitLabel + mandatoryBadge + '</span>'
                + '</div>' + qtyHtml + '</div>';
        }).join('') + '</div>';
}

function newCalcToggleTillval(idx){
    var tv = _newCalcContext.tillval[idx];
    if(!tv || tv.included) return;
    tv.checked = !tv.checked;
    _persistSub();
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
    if(p){ _renderInlineTillval(p); _renderInlinePriceBox(p); _renderNewCalcSidebar(); }
}

function newCalcSetTillvalQty(idx, val){
    var tv = _newCalcContext.tillval[idx];
    if(!tv) return;
    var n = parseFloat(val);
    if(!isFinite(n)) n = 0;
    if(tv.linkMin != null && n < tv.linkMin) n = tv.linkMin;
    if(tv.linkMax != null && n > tv.linkMax) n = tv.linkMax;
    tv.userQty = n;
    _persistSub();
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
    if(p){ _renderInlinePriceBox(p); _renderNewCalcSidebar(); }
}

function newCalcAddToKalkyl(){
    var p = (typeof catalogProducts !== 'undefined' && catalogProducts.find) ? catalogProducts.find(function(x){return x.id===_newCalcContext.selectedProductId;}) : null;
    if(!p) return;
    // Obligatorisk-validering: alla `included` tillval som har valbara varianter (select) måste ha ett val.
    var missing = _newCalcContext.tillval.filter(function(tv){
        if(!tv || !tv.included) return false;
        var hasVariants = Array.isArray(tv.variants) && tv.variants.length > 0;
        if(!hasVariants) return false;
        return !tv.selectedVariant;
    });
    if(missing.length){
        var names = missing.map(function(tv){ return tv.name; }).join(', ');
        alert('Välj alternativ för obligatoriska tillval: ' + names);
        return;
    }
    var t = _calcInlineTotal(p);
    var vinfo = _getVariantInfo(p);
    var variantLabel = '';
    if(vinfo.hasVariants && t.variant && vinfo.labelKey){
        var v = t.variant[vinfo.labelKey];
        if(v !== undefined && v !== null && v !== '') variantLabel = v + vinfo.labelSuffix;
    }
    var description = p.name + (variantLabel ? ' - ' + variantLabel : '');
    // Alla checked tillval räknas med i entry — inklusive dolda (default_on/mandatory).
    var tillvalDetailed = _newCalcContext.tillval.filter(function(tv){ return tv.checked; }).map(function(tv){
        var eq = _getInlineEffectiveTillvalQty(tv);
        return {
            id: tv.id, name: tv.name, price: tv.price, qty: eq, total: tv.price * eq, unit: tv.unit,
            value: tv.selectedVariant || '',
            hidden: !!tv.hidden,
            is_service: !!tv.is_service,
            rot_eligible: !!(tv.rotEligible || tv.rot_eligible),
            green_tech_eligible: !!(tv.greenTechEligible || tv.green_tech_eligible),
            tax_type: tv.taxType || (tv.rotEligible ? 'ROT' : (tv.greenTechEligible ? 'GT20' : 'NONE'))
        };
    });
    var cat = _newCalcContext.categoryId;
    var editId = _newCalcContext.editEntryId;
    var taxType = t.taxType || 'NONE';
    var deductionLabel = '';
    if(t.deduction > 0){
        if(taxType === 'GT50') deductionLabel = 'Skattereduktion (GT50)';
        else if(taxType === 'GT20') deductionLabel = 'Grönt teknik-avdrag (GT20)';
        else if(taxType === 'ROT') deductionLabel = 'ROT-avdrag';
        else if(taxType === 'RUT') deductionLabel = 'RUT-avdrag';
        else deductionLabel = 'Skatteavdrag';
    }
    var entry = {
        description: description,
        product_id: p.id,
        product_name: p.name,
        variant_label: variantLabel,
        variant_idx: _newCalcContext.selectedVariantIdx,
        qty: t.qty,
        unit_price: t.basePrice,
        subtotal: t.gross,                      // GROSS pris (före avdrag)
        tillval: tillvalDetailed,
        tillvalTotal: t.tillvalTotal,
        tax_type: taxType,
        deduction_amount: t.deduction || 0,     // POTENTIELLT avdrag (sidebaren bestämmer hur mycket som faktiskt dras)
        deduction_label: deductionLabel,
        total: t.gross                          // GROSS — sidebaren räknar Att betala = subtotal - effektivt avdrag
    };
    if(editId && typeof prisUpdateEntry === 'function'){
        prisUpdateEntry(cat, editId, entry);
    } else if(typeof prisCommitSubSession === 'function'){
        prisCommitSubSession(cat, entry);
    }
    // Ladda in en TOM sub-session för kategorin efter commit
    _newCalcContext = (typeof prisGetSubSession === 'function')
        ? prisGetSubSession(cat)
        : { categoryId: cat, selectedProductId: null, tillval: [], productQty: 1, selectedVariantIdx: 0 };
    _renderNewCalcSidebar();
    if(editId && typeof openCalcSummary === 'function'){
        setTimeout(openCalcSummary, 100);
    } else {
        newCalcBackToGrid();
    }
}

function _renderNewCalcSidebar(){
    var sidebar = document.getElementById('newCalcPrisSidebar');
    if(!sidebar) return;
    if(typeof renderPrisSidebar !== 'function'){
        sidebar.innerHTML = '<div style="padding:12px;background:#fee;color:#b00;border-radius:8px;font-size:12px">renderPrisSidebar saknas (prissammanstallning.js inte laddad)</div>';
        return;
    }
    // Aggregera GROSS subtotal + POTENTIELLT avdrag från varukorgen
    var subtotal = 0;
    var potentialDed = 0;
    var aggTaxType = 'NONE';
    if(typeof _kalkylItems !== 'undefined' && _kalkylItems){
        Object.keys(_kalkylItems).forEach(function(k){
            var item = _kalkylItems[k];
            if(!item) return;
            var entries = Array.isArray(item.entries) ? item.entries : [item];
            entries.forEach(function(e){
                var gross = parseFloat(e.subtotal || 0) || parseFloat(e.total || 0);
                subtotal += gross;
                potentialDed += parseFloat(e.deduction_amount) || 0;
                if(aggTaxType === 'NONE' && e.tax_type && e.tax_type !== 'NONE') aggTaxType = e.tax_type;
            });
        });
    }

    // Effektivt avdrag = min(potentiellt, owners × maxPerPerson, slider)
    var taxType = aggTaxType;
    var deductType = _newCalcContext.deductType;
    if(!deductType){
        deductType = potentialDed > 0 ? ((taxType === 'ROT') ? 'rot' : 'green') : 'none';
    }
    var owners = _newCalcContext.owners || 1;
    var tax = (typeof taxSettings !== 'undefined') ? taxSettings : { rotMax: 50000, gt50: 50, gt20: 20, rot: 30, moms: 25 };
    var maxPerPerson = (taxType === 'ROT') ? (tax.rotMax || 50000) : 50000;
    var ownerCap = owners * maxPerPerson;
    var sliderMax = ownerCap;
    var sliderValue = (_newCalcContext.sliderValue != null) ? Math.min(parseFloat(_newCalcContext.sliderValue), sliderMax) : sliderMax;
    var effectiveDed = (deductType === 'none') ? 0 : Math.min(potentialDed, sliderValue);

    renderPriceSummary('newCalcPrisSidebar', {
        lines: [],
        subtotal: subtotal,
        totalIsAll: true,
        taxType: taxType,
        deductType: deductType,
        deductAmount: effectiveDed,
        owners: owners,
        sliderInteractive: true,
        sliderValue: sliderValue,
        maxDeduction: sliderMax,
        total: subtotal - effectiveDed,
        finYears: _newCalcContext.finYears || 15,
        monthly: 0,
        category: _newCalcContext.categoryId,
        onDeductChange: 'newCalcSetDeductType',
        onOwnerChange: 'newCalcSetOwners',
        onFinChange: 'newCalcSetFinYears',
        onSliderChange: 'newCalcSetSliderValue'
    });
}

window.newCalcOpenProduct = newCalcOpenProduct;
window.newCalcBackToGrid = newCalcBackToGrid;
window.newCalcSetProductQty = newCalcSetProductQty;
window.newCalcSetVariant = newCalcSetVariant;
window.newCalcToggleTillval = newCalcToggleTillval;
window.newCalcSetTillvalQty = newCalcSetTillvalQty;
window.newCalcAddToKalkyl = newCalcAddToKalkyl;

// Sidebar-kontroller: ägare, skatteavdrag-typ, kvarvarande avdrag, finansiering
function newCalcSetOwners(n){
    _newCalcContext.owners = parseInt(n) || 1;
    // Återställ reglaget när antal personer byts (max-värdet ändras)
    _newCalcContext.sliderValue = null;
    _persistSub();
    _renderNewCalcSidebar();
}
function newCalcSetDeductType(type){
    _newCalcContext.deductType = type || 'none';
    _persistSub();
    _renderNewCalcSidebar();
}
function newCalcSetSliderValue(v){
    _newCalcContext.sliderValue = parseFloat(v) || 0;
    _persistSub();
    _renderNewCalcSidebar();
}
function newCalcSetFinYears(y){
    _newCalcContext.finYears = parseInt(y) || 0;
    _persistSub();
    _renderNewCalcSidebar();
}
window.newCalcSetOwners = newCalcSetOwners;
window.newCalcSetDeductType = newCalcSetDeductType;
window.newCalcSetSliderValue = newCalcSetSliderValue;
window.newCalcSetFinYears = newCalcSetFinYears;

// Varna innan fliken stängs/navigeras om det finns en påbörjad kalkyl
window.addEventListener('beforeunload', function(e){
    if(_hasUnsavedCalc()){
        e.preventDefault();
        e.returnValue = '';
        return '';
    }
});

// new_calc.js — Spara kalkyl. ÅSIDOSÄTTER legacy saveCfgQuote() som kraschade
// med "Cannot set properties of null" eftersom den läste från gamla _batProduct,
// _lbSelected etc. som ALDRIG sätts av nya flödet. Vi sparar _kalkylItems direkt.
async function newCalcSaveQuote(){
    if(typeof _kalkylItems === 'undefined' || !_kalkylItems || !Object.keys(_kalkylItems).length){
        alert('Kalkylen är tom — lägg till produkter först.');
        return;
    }
    var items = {};
    var cats = [];
    var grandTotal = 0;
    Object.keys(_kalkylItems).forEach(function(cat){
        var item = _kalkylItems[cat];
        if(!item || !item.total) return;
        items[cat] = item;
        // Interna nycklar (_addons, _addonsState, _summary osv) ska inte synas i produkt-listan
        if(cat.charAt(0) !== '_') cats.push(cat);
        grandTotal += parseFloat(item.total) || 0;
    });
    if(!cats.length){ alert('Inga produkter i kalkylen.'); return; }

    var customer = (typeof pendingKalkylCustomer !== 'undefined' && pendingKalkylCustomer) ? pendingKalkylCustomer : null;
    var name = '';
    var isNew = !(typeof currentQuoteId !== 'undefined' && currentQuoteId);
    if(isNew){
        var defaultName = (customer && customer.name) ? customer.name : cats.join(', ');
        name = prompt('Ge kalkylen ett namn:', defaultName);
        if(!name) return;
    }
    var description = name || (customer && customer.name) || cats.join(', ');

    var payload = {
        category: cats.join(','),
        config_data: JSON.stringify({ items: items, kalkylName: description, customer: customer }),
        total_price: grandTotal,
        panel_name: description,
        panel_count: 0,
        status: 'utkast',
        created_by: (typeof gStaffId !== 'undefined' ? gStaffId : null),
        customer_name: customer ? customer.name : null,
        customer_address: customer ? customer.address : null,
        customer_email: customer ? customer.email : null,
        customer_phone: customer ? customer.phone : null,
        customer_personnummer: (customer && customer.owner1) ? customer.owner1.pnr : null,
        owner_count: customer ? (customer.ownerType === '2' ? 2 : (customer.ownerType === 'brf' ? 0 : 1)) : 1,
        prospect_data: customer ? (customer.solarData || null) : null
    };
    if(typeof currentQuoteId !== 'undefined' && currentQuoteId) payload.id = currentQuoteId;

    try {
        var res = await fetch('/api/quotes.php', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
        var data = await res.json();
        if(data.error){ alert('Fel: ' + data.error); return; }
        if(data.id){ currentQuoteId = data.id; }
        if(typeof _cfgDirty !== 'undefined') _cfgDirty = false;
        alert('Kalkylen "' + description + '" är sparad.');
        if(typeof loadCalcList === 'function') loadCalcList();
    } catch(e){
        console.error('[new_calc] save error', e);
        alert('Kunde inte spara: ' + (e.message || e));
    }
}
window.newCalcSaveQuote = newCalcSaveQuote;
// Åsidosätt legacy saveCfgQuote — MÅSTE göras EFTER konfigurator-spara.js har laddat
// (annars skriver dess "async function saveCfgQuote(){}" över window.saveCfgQuote igen).
function _newCalcInstallSaveOverride(){
    window.saveCfgQuote = newCalcSaveQuote;
}
if(document.readyState === 'complete' || document.readyState === 'interactive'){
    setTimeout(_newCalcInstallSaveOverride, 0);
} else {
    document.addEventListener('DOMContentLoaded', _newCalcInstallSaveOverride);
}
window.addEventListener('load', _newCalcInstallSaveOverride);

// Routa ALLA showKalkylSummary-anrop (även från legacy openQuoteFromList) till nya
// Calc_summary-sidan. Installeras via DOMContentLoaded så vi vinner över andra script.
function _newCalcInstallSummaryRedirect(){
    var origShowKalkylSummary = window.showKalkylSummary;
    if(window._showKalkylSummaryRedirected) return;
    window._showKalkylSummaryRedirected = true;
    window._origShowKalkylSummary = origShowKalkylSummary;
    window.showKalkylSummary = function(){
        // Städa upp CalcBuilder render-arean
        var renderArea = document.getElementById('newCalcRenderArea');
        if(renderArea) renderArea.remove();
        var banner = document.getElementById('newCalcLegacyBanner');
        if(banner) banner.remove();
        var lbl = document.getElementById('cfgFileLabel');
        if(lbl) lbl.textContent = '';
        if(typeof _newCalcContext !== 'undefined'){
            _newCalcContext.selectedProductId = null;
            _newCalcContext.tillval = [];
        }
        var bar = document.getElementById('cfgCategoryBar');
        if(bar) bar.style.display = 'none';
        // Öppna den NYA Kalkylsammanställning-sidan (Calc_summary.php/js/css)
        if(typeof openCalcSummary === 'function'){
            openCalcSummary();
            return;
        }
        if(typeof origShowKalkylSummary === 'function') origShowKalkylSummary();
    };
}
if(document.readyState === 'complete' || document.readyState === 'interactive'){
    setTimeout(_newCalcInstallSummaryRedirect, 0);
} else {
    document.addEventListener('DOMContentLoaded', _newCalcInstallSummaryRedirect);
}
window.addEventListener('load', _newCalcInstallSummaryRedirect);

// Routa öga/penna i prissammanställningen till nya CalcBuilder-flödet
// Installeras EFTER DOMContentLoaded så kalkyl-summary.js redan har deklarerat sina funktioner
function _newCalcInstallSummaryHooks(){
    if(_newCalcInstallSummaryHooks._done) return;
    _newCalcInstallSummaryHooks._done = true;
    function _setEditBtnLoading(btn, on){
        if(!btn) return;
        if(on){
            btn.disabled = true;
            btn.setAttribute('data-loading', '1');
            btn.style.opacity = '.35';
            btn.style.cursor = 'wait';
            btn.style.pointerEvents = 'none';
        } else {
            btn.disabled = false;
            btn.removeAttribute('data-loading');
            btn.style.opacity = '';
            btn.style.cursor = '';
            btn.style.pointerEvents = '';
        }
    }
    function tryNewCalc(cat, productId, entryId, entry, btnEl){
        var summaryView = document.getElementById('summaryProductList');
        if(summaryView) summaryView.style.display = 'none';
        if(!productId){
            return Promise.resolve(openNewCalcCategory(cat, { edit: true }));
        }
        // openNewCalcCategory returnerar Promise som resolvar EFTER render (scripts körda synkront efter fetch).
        var p = openNewCalcCategory(cat, { edit: true });
        if(!p || typeof p.then !== 'function'){ p = Promise.resolve(); }
        return p.then(function(){
            // Välj rätt edit-flöde baserat på KATEGORINS builder_mode, inte om WindowSnippet
            // råkar vara kvar i global scope från en tidigare fönster-kalkyl.
            var isCode = _newCalcContext && _newCalcContext.cfg && _newCalcContext.cfg.builder_mode === 'code';
            if(isCode && entry && window.WindowSnippet && typeof window.WindowSnippet.openForEdit === 'function'){
                try { window.WindowSnippet.openForEdit(entry); }
                catch(e){ console.warn('openForEdit fel', e); }
            } else if(typeof newCalcOpenProduct === 'function'){
                newCalcOpenProduct(productId, entryId);
            }
        });
    }
    window.editFromSummary = function(cat, btnEl){
        _setEditBtnLoading(btnEl, true);
        return tryNewCalc(cat)
            .catch(function(e){ console.warn('editFromSummary fel', e); })
            .then(function(){ _setEditBtnLoading(btnEl, false); });
    };
    window.editSummaryEntry = function(cat, entryId, btnEl){
        _setEditBtnLoading(btnEl, true);
        // Om vi står i Calc_summary-vyn (prissammanstallningen där), stäng först så konfiguratorn kan visas
        var calcSumEl = document.getElementById('page-calc-summary');
        var calcSumOpen = calcSumEl && calcSumEl.classList.contains('active');
        if(calcSumOpen && typeof closeCalcSummary === 'function'){
            try { closeCalcSummary(); } catch(e){}
        }
        var item = (window._kalkylItems || {})[cat];
        var entries = (item && Array.isArray(item.entries)) ? item.entries : (item ? [item] : []);
        var entry = entries.find(function(e){ return e.entry_id === entryId; }) || null;
        var pid = entry && (entry.product_id || entry.productId);
        if(!pid && entry && typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)){
            var name = entry.product_name || entry.description;
            var match = catalogProducts.find(function(p){
                return p && p.cat === cat && (p.name === name || (name && p.name && name.indexOf(p.name) !== -1));
            });
            if(match) pid = match.id;
        }
        return tryNewCalc(cat, pid, entryId, entry, btnEl)
            .catch(function(e){ console.warn('editSummaryEntry fel', e); })
            .then(function(){ _setEditBtnLoading(btnEl, false); });
    };
    window.showSummaryCategory = function(cat, btnEl){
        _setEditBtnLoading(btnEl, true);
        return tryNewCalc(cat)
            .catch(function(e){ console.warn('showSummaryCategory fel', e); })
            .then(function(){ _setEditBtnLoading(btnEl, false); });
    };
}

// Kör hooks så fort DOM + alla scripts laddats
if(document.readyState === 'complete' || document.readyState === 'interactive'){
    setTimeout(_newCalcInstallSummaryHooks, 0);
} else {
    document.addEventListener('DOMContentLoaded', _newCalcInstallSummaryHooks);
}
window.addEventListener('load', _newCalcInstallSummaryHooks);

// Gör funktionerna globalt tillgängliga (anropas från onclick-attribut och andra filer)
window.newCalc = newCalc;
window.openNewCalcCategory = openNewCalcCategory;
window._showNewCalcGrid = _showNewCalcGrid;
window._loadNewCalcCards = _loadNewCalcCards;
window._newCalcActive = _newCalcActive;

// ============================================================
// SMARTFILTER — per säljare, sparat i DB (user_calc_filters)
// Aktiveras när cfg.smart_filter = 1 i CalcBuilder.
// ============================================================
var _smartFilterCache = {};      // { catId: [filter,...] }
var _smartFilterActiveId = {};   // { catId: id }
var _smartFilterLoading = {};

function _smartFilterStaffId(){
    try {
        if (typeof gStaffId !== 'undefined' && gStaffId) return gStaffId;
        var email = localStorage.getItem('userEmail') || '';
        if (typeof staffList !== 'undefined' && Array.isArray(staffList)) {
            var s = staffList.find(function(x){ return x.email === email; });
            if (s) return s.id;
        }
        // Fallback: prova localStorage userId, annars Jim (43) så admin kan testa
        var uid = parseInt(localStorage.getItem('userId') || localStorage.getItem('staffId') || '0');
        if (uid > 0) return uid;
    } catch(e){}
    return 43; // Jim = default fallback (testbart även när inte inloggad som säljare)
}

function _smartFilterModelLabels(p){
    if (!p || !p.specs) return [];
    var labels = {};
    if (Array.isArray(p.specs.models)) p.specs.models.forEach(function(m){ if (m && m.label) labels[m.label] = true; });
    if (Array.isArray(p.specs.variants)) p.specs.variants.forEach(function(v){
        if (!v) return;
        var lbl = v.label || v.name || (v.kwh ? v.kwh + ' kWh' : '') || (v.paneler ? v.paneler + ' paneler' : '') || (v.cm ? v.cm + ' cm' : '');
        if (lbl) labels[lbl] = true;
    });
    if (Array.isArray(p.specs.styles)) p.specs.styles.forEach(function(s){ if (s && s.style) labels[s.style] = true; });
    return Object.keys(labels);
}

function _smartFilterActive(catId){
    var id = _smartFilterActiveId[catId];
    if (!id) return null;
    var arr = _smartFilterCache[catId] || [];
    return arr.find(function(f){ return f.id === id; }) || null;
}

function _smartFilterLoad(catId){
    if (_smartFilterCache[catId] || _smartFilterLoading[catId]) return Promise.resolve(_smartFilterCache[catId] || []);
    var sid = _smartFilterStaffId();
    if (!sid){ _smartFilterCache[catId] = []; return Promise.resolve([]); }
    _smartFilterLoading[catId] = true;
    return fetch('/api/calc_filters.php?staff_id=' + sid + '&category_id=' + encodeURIComponent(catId))
        .then(function(r){ return r.json(); })
        .then(function(d){
            _smartFilterCache[catId] = (d && d.success && Array.isArray(d.filters)) ? d.filters : [];
            _smartFilterLoading[catId] = false;
            return _smartFilterCache[catId];
        })
        .catch(function(){ _smartFilterLoading[catId] = false; _smartFilterCache[catId] = []; return []; });
}

function _renderSmartFilterBar(catId, products){
    var host = document.getElementById('newCalcSmartFilter');
    if (!host) return;
    _smartFilterLoad(catId).then(function(filters){
        var activeId = _smartFilterActiveId[catId];
        var html = '<div style="display:flex;flex-wrap:wrap;gap:6px;align-items:center">';
        html += '<span style="font-size:11px;font-weight:700;color:#92400e;margin-right:6px">FILTER:</span>';
        if (filters.length === 0){
            html += '<span style="font-size:12px;color:#a16207;font-style:italic">Inga sparade — skapa ett genom att klicka knappen.</span>';
        } else {
            filters.forEach(function(f){
                var active = activeId === f.id;
                html += '<span style="display:inline-flex;align-items:center;gap:0">'
                    + '<button onclick="smartFilterToggle(\'' + _esc(catId) + '\',' + f.id + ')" style="padding:6px 12px;border:1.5px solid ' + (active ? '#f59e0b' : '#fde68a') + ';background:' + (active ? '#f59e0b' : '#fffbeb') + ';color:' + (active ? '#fff' : '#92400e') + ';border-radius:20px 0 0 20px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">' + _esc(f.name) + '</button>'
                    + '<button onclick="smartFilterDelete(\'' + _esc(catId) + '\',' + f.id + ')" title="Ta bort" style="padding:6px 8px;border:1.5px solid ' + (active ? '#f59e0b' : '#fde68a') + ';border-left:none;background:' + (active ? '#f59e0b' : '#fffbeb') + ';color:' + (active ? '#fff' : '#dc2626') + ';border-radius:0 20px 20px 0;font-size:12px;cursor:pointer;font-family:inherit">×</button>'
                    + '</span>';
            });
            if (activeId){
                html += '<button onclick="smartFilterClear(\'' + _esc(catId) + '\')" style="padding:6px 10px;border:1px solid #e5e7eb;background:#fff;color:#64748b;border-radius:20px;font-size:11px;cursor:pointer;font-family:inherit;margin-left:4px">Visa alla</button>';
            }
        }
        html += '<button onclick="smartFilterShowCreate(\'' + _esc(catId) + '\')" style="margin-left:auto;padding:6px 14px;background:#024550;color:#fff;border:none;border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit">+ Skapa filter</button>';
        html += '</div>';
        host.innerHTML = '<div style="padding:10px 14px;background:#fffbeb;border:1px dashed #fcd34d;border-radius:10px">' + html + '</div>';
    });
}

function _smartFilterNotify(catId){
    // Re-rendera filter-baren + meddela snippets/simple-paint
    _renderSmartFilterBar(catId, []);
    try {
        document.dispatchEvent(new CustomEvent('smartFilter:changed', { detail: { categoryId: catId, active: _smartFilterActive(catId) } }));
    } catch(e){}
    // Simple-mode: paint griden om vi är i simple
    var grid = document.getElementById('newCalcSimpleGrid');
    if (grid && typeof window._newCalcRepaintSimple === 'function'){
        try { window._newCalcRepaintSimple(catId); } catch(e){}
    }
}

function smartFilterToggle(catId, id){
    _smartFilterActiveId[catId] = (_smartFilterActiveId[catId] === id) ? null : id;
    _smartFilterNotify(catId);
}
function smartFilterClear(catId){
    _smartFilterActiveId[catId] = null;
    _smartFilterNotify(catId);
}
function smartFilterDelete(catId, id){
    if (!confirm('Ta bort filtret?')) return;
    var sid = _smartFilterStaffId();
    fetch('/api/calc_filters.php?id=' + id + '&staff_id=' + sid, { method:'DELETE' })
        .then(function(r){ return r.json(); })
        .then(function(){
            // Ta bort direkt ur cache så bar:en uppdateras utan re-fetch race
            if (Array.isArray(_smartFilterCache[catId])){
                _smartFilterCache[catId] = _smartFilterCache[catId].filter(function(f){ return f.id !== id; });
            }
            _smartFilterLoading[catId] = false;
            if (_smartFilterActiveId[catId] === id) _smartFilterActiveId[catId] = null;
            _smartFilterNotify(catId);
        });
}

function smartFilterShowCreate(catId){
    var src = (typeof catalogProducts !== 'undefined' && Array.isArray(catalogProducts)) ? catalogProducts : [];
    var products = src.filter(function(p){ return p && p.cat === catId; })
        .sort(function(a,b){ return String(a.name||'').localeCompare(String(b.name||''),'sv'); });
    var modelSet = {};
    products.forEach(function(p){ _smartFilterModelLabels(p).forEach(function(m){ modelSet[m] = true; }); });
    var models = Object.keys(modelSet).sort();

    var modal = document.createElement('div');
    modal.id = 'smartFilterModal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:10000;display:flex;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(3px)';
    modal.onclick = function(e){ if (e.target === modal) modal.remove(); };

    var prodChecks = products.map(function(p){
        return '<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:13px"><input type="checkbox" class="sf-prod" value="' + _esc(p.id) + '" checked style="width:16px;height:16px;accent-color:#024550">' + _esc(p.name||'') + '</label>';
    }).join('') || '<div style="color:#94a3b8;font-size:12px">Inga produkter</div>';
    var modelChecks = models.map(function(m){
        return '<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;font-size:13px"><input type="checkbox" class="sf-model" value="' + _esc(m) + '" checked style="width:16px;height:16px;accent-color:#3b82f6">' + _esc(m) + '</label>';
    }).join('') || '<div style="color:#94a3b8;font-size:12px;font-style:italic">Inga modeller hittades</div>';

    modal.innerHTML = '<div style="background:#fff;border-radius:16px;padding:24px;width:560px;max-width:95vw;max-height:85vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,.25)">'
        + '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">'
        +   '<h3 style="font-size:18px;font-weight:700;margin:0">Skapa filter</h3>'
        +   '<button onclick="document.getElementById(\'smartFilterModal\').remove()" style="background:none;border:none;font-size:22px;cursor:pointer;color:#94a3b8">×</button>'
        + '</div>'
        + '<div style="margin-bottom:16px"><label style="font-size:12px;font-weight:700;color:#64748b;display:block;margin-bottom:4px">FILTERNAMN</label>'
        +   '<input id="sfName" placeholder="t.ex. Mitt special" style="width:100%;padding:10px 14px;border:1.5px solid #e5e7eb;border-radius:8px;font-size:14px;font-family:inherit;box-sizing:border-box"></div>'
        + '<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px">'
        +   '<div><div style="font-size:12px;font-weight:700;color:#64748b;margin-bottom:8px">PRODUKTER</div>' + prodChecks + '</div>'
        +   '<div><div style="font-size:12px;font-weight:700;color:#64748b;margin-bottom:8px">MODELLER</div>' + modelChecks + '</div>'
        + '</div>'
        + '<button onclick="smartFilterDoSave(\'' + _esc(catId) + '\')" style="margin-top:20px;width:100%;padding:14px;background:#f59e0b;color:#fff;border:none;border-radius:10px;font-size:15px;font-weight:700;cursor:pointer;font-family:inherit">Spara filter</button>'
        + '</div>';
    document.body.appendChild(modal);
    setTimeout(function(){ document.getElementById('sfName').focus(); }, 50);
}

function smartFilterDoSave(catId){
    var name = document.getElementById('sfName').value.trim();
    if (!name){ alert('Ange ett namn för filtret'); return; }
    var prods = [], models = [];
    document.querySelectorAll('.sf-prod:checked').forEach(function(cb){ prods.push(cb.value); });
    document.querySelectorAll('.sf-model:checked').forEach(function(cb){ models.push(cb.value); });
    var sid = _smartFilterStaffId();
    if (!sid){ alert('Kunde inte identifiera säljare'); return; }
    fetch('/api/calc_filters.php', {
        method:'POST',
        headers:{'Content-Type':'application/json'},
        body: JSON.stringify({ staff_id: sid, category_id: catId, name: name, products: prods, models: models })
    })
    .then(function(r){ return r.json(); })
    .then(function(d){
        if (d && d.success){
            // Push direkt i cache så bar:en uppdateras utan att vänta på re-fetch
            // (race condition: _smartFilterLoading kan vara stuck → re-fetch returnerar tom)
            if (!Array.isArray(_smartFilterCache[catId])) _smartFilterCache[catId] = [];
            _smartFilterCache[catId].push({ id: d.id, name: name, products: prods, models: models, sort_order: 0 });
            _smartFilterLoading[catId] = false;
            _smartFilterActiveId[catId] = d.id;
            var m = document.getElementById('smartFilterModal');
            if (m) m.remove();
            _smartFilterNotify(catId);
        } else {
            alert('Kunde inte spara: ' + ((d && d.error) || 'okänt fel'));
        }
    });
}

window.smartFilterToggle = smartFilterToggle;
window.smartFilterClear = smartFilterClear;
window.smartFilterDelete = smartFilterDelete;
window.smartFilterShowCreate = smartFilterShowCreate;
window.smartFilterDoSave = smartFilterDoSave;

// Publik API för code-mode snippets:
//   window.smartFilterActive(catId)  -> { id, name, products:[], models:[] } eller null
//   window.smartFilterApply(catId, products) -> filtrerad array baserat på aktivt filter
//   window.smartFilterModels(p)      -> array med model-labels på en produkt
window.smartFilterActive = function(catId){ return _smartFilterActive(catId); };
window.smartFilterModels = function(p){ return _smartFilterModelLabels(p); };
window.smartFilterApply = function(catId, arr){
    var f = _smartFilterActive(catId);
    if (!f) return arr;
    var out = arr;
    if (Array.isArray(f.products) && f.products.length){
        out = out.filter(function(p){ return f.products.indexOf(p.id) !== -1; });
    }
    if (Array.isArray(f.models) && f.models.length){
        out = out.filter(function(p){
            var m = _smartFilterModelLabels(p);
            return m.some(function(x){ return f.models.indexOf(x) !== -1; });
        });
    }
    return out;
};