/**
 * item-wizard.js
 * Generic multi-step item wizard — shared by quotes and jobs.
 * Configured via pageMeta (itemsFieldPrefix, pricingUrl, etc.)
 *
 * Step 1: Choose item type (Product / Custom / Flat-Rate / Labor)
 * Step 2: Gather item details (fields vary by type)
 * Step 3: Coating layers (Product & Custom only; skipped for Flat-Rate & Labor)
 *
 * Auto-calculates pricing after each item add/edit/remove.
 */

// ─── State ────────────────────────────────────────────────────────────────────

let quoteItems = [];   // Array of item objects matching CreateQuoteItemDto shape

// Custom Powder Order preview state — tracks the auto-calculated material cost and any
// user override (e.g. adding shipping).  Reset when the server says no powder is needed.
let _customPowderAutoAmount = 0;    // last server-calculated material cost
let _customPowderUserAmount = null; // null = use auto; number = user override
let _customPowderPreviewLabel = 'Custom Powder Order';
let _pricingPayloadHadUserPowderItem = false; // set just before each pricing fetch

const wz = {           // Wizard state
    step: 1,
    editIndex: -1,     // -1 = new item; >= 0 = editing
    itemType: null,    // 'product' | 'calculated' | 'generic' | 'labor' | 'ai' | 'formula'
    data: {},          // Collected field values
    ai: {              // AI-specific wizard state
        phase: 'upload',  // 'upload' | 'loading' | 'followup' | 'result'
        tempIds: [],      // Temp photo IDs from server
        fileNames: [],    // Display file names
        result: null,     // Last AiAnalyzeItemResult from server
        conversationHistory: [],
        accepted: false,
        tags: []          // Editable tags (AI-generated + user additions)
    }
};

// ─── Page metadata (from embedded JSON) ───────────────────────────────────────

let pageMeta   = {};
let powderData = [];
let catalogData = [];
let merchandiseData = [];
let supplierData = [];
let prepServiceData = [];
let blastSetupData  = [];

document.addEventListener('DOMContentLoaded', () => {
    const metaEl = document.getElementById('quoteMetaData');
    if (metaEl) pageMeta = JSON.parse(metaEl.textContent);

    const powderEl = document.getElementById('inventoryPowdersData');
    if (powderEl) powderData = JSON.parse(powderEl.textContent);

    const catalogEl = document.getElementById('catalogItemsData');
    if (catalogEl) catalogData = JSON.parse(catalogEl.textContent);

    const merchEl = document.getElementById('merchandiseItemsData');
    if (merchEl) merchandiseData = JSON.parse(merchEl.textContent);

    const supplierEl = document.getElementById('vendorsData');
    if (supplierEl) supplierData = JSON.parse(supplierEl.textContent);

    const prepEl = document.getElementById('prepServicesData');
    if (prepEl) prepServiceData = JSON.parse(prepEl.textContent);

    const blastEl = document.getElementById('blastSetupsData');
    if (blastEl) blastSetupData = JSON.parse(blastEl.textContent);

    // Restore items from server round-trip (validation failure re-render)
    const existingEl = document.getElementById('existingItemsData');
    if (existingEl) {
        try {
            quoteItems = JSON.parse(existingEl.textContent);
            renderAllCards();
            writeHiddenFields();
            window.scrollTo({ top: 0, behavior: 'instant' });
            scheduleAutoPricing();
        } catch (err) {
            console.error('item-wizard: failed to restore items from server model:', err);
        }
    }

    // Guarantee hidden fields are always written on form submission, even if the wizard
    // was never interacted with (e.g. validation round-trip with pre-existing items).
    const hfc = document.getElementById('hiddenFieldsContainer');
    const ownerForm = hfc?.closest('form');
    if (ownerForm) {
        ownerForm.addEventListener('submit', writeHiddenFields, { capture: true });

        // Save scroll position before the form causes a full-page reload so we can
        // restore it after the server redirects back to this page on a validation error.
        // Key is path-specific; cleared on pagehide unless we're leaving via a submit so
        // a fresh navigation to this page never restores a stale position.
        const scrollKey = 'wizardScrollY:' + location.pathname;
        let wizardSubmitting = false;
        ownerForm.addEventListener('submit', () => {
            wizardSubmitting = true;
            sessionStorage.setItem(scrollKey, String(Math.round(window.scrollY)));
        }, { capture: true });

        // If the page unloads for any reason other than our own form submit (e.g. the
        // user clicks a nav link or the server redirects to a success page), discard the
        // saved position so it doesn't fire on the next fresh visit.
        window.addEventListener('pagehide', () => {
            if (!wizardSubmitting) sessionStorage.removeItem(scrollKey);
        });

        // Restore on load — fire after layout is painted so scrollTo lands correctly.
        const savedY = sessionStorage.getItem(scrollKey);
        if (savedY !== null) {
            sessionStorage.removeItem(scrollKey);
            requestAnimationFrame(() => window.scrollTo({ top: parseInt(savedY, 10), behavior: 'instant' }));
        }
    }

    // Close any open powder combobox or catalog lookup dropdown when clicking outside it
    document.addEventListener('click', e => {
        document.querySelectorAll('[id^="coat_powder_wrapper_"]').forEach(wrapper => {
            if (!wrapper.contains(e.target)) {
                const idx = wrapper.id.replace('coat_powder_wrapper_', '');
                powderComboClose(parseInt(idx));
            }
        });
        document.querySelectorAll('[id^="coat_catalog_results_"]').forEach(dd => {
            const idx = dd.id.replace('coat_catalog_results_', '');
            const wrapper = document.getElementById(`coat_catalog_search_wrapper_${idx}`);
            if (!wrapper?.contains(e.target) && !dd.contains(e.target))
                dd.style.display = 'none';
        });
    });
});

// ─── Wizard open / close ──────────────────────────────────────────────────────

function openWizard(editIndex = -1) {
    wz.editIndex = editIndex;
    wz.step = 1;
    wz.data = {};
    wz.ai = { phase: 'upload', tempIds: [], fileNames: [], previewUrls: [], result: null, conversationHistory: [], accepted: false, tags: [], followUpQuestion: null };

    if (editIndex >= 0) {
        const item = quoteItems[editIndex];
        wz.itemType = item.isLaborItem          ? 'labor'
                    : item.isSalesItem          ? 'sales'
                    : item.isGenericItem        ? 'generic'
                    : item.catalogItemId        ? 'product'
                    : item.isAiItem             ? 'ai'
                    : item.isCustomFormulaItem  ? 'formula'
                    : 'calculated';
        // Pre-fill wizard data from existing item
        wz.data = JSON.parse(JSON.stringify(item)); // deep copy
        // Restore sales item catalog reference
        if (wz.itemType === 'sales') {
            wz.data.salesCatalogItemId = item.catalogItemId || null;
        }
        // Restore AI state if editing an AI item
        if (wz.itemType === 'ai') {
            wz.ai.tempIds = item.aiPhotoTempIds || [];
            wz.ai.fileNames = item.aiPhotoFileNames || [];
            wz.ai.previewUrls = item.aiPhotoPreviewUrls || [];
            wz.ai.tags = item.aiTags ? item.aiTags.split(',').filter(Boolean) : [];
            wz.ai.accepted = true;
            wz.ai.phase = 'result';
            // Reconstruct a synthetic result from saved values so the item can be saved
            // without forcing another round-trip to the AI engine. confidence=null signals
            // the card to render as "Saved Estimate" instead of "AI Estimate Ready".
            wz.ai.result = {
                description:        item.description || '',
                surfaceAreaSqFt:    item.surfaceAreaSqFt || 0,
                complexity:         item.complexity || 'Moderate',
                estimatedMinutes:   item.estimatedMinutes || 30,
                estimatedUnitPrice: item.manualUnitPrice || 0,
                estimatedTotal:     (item.manualUnitPrice || 0) * (item.quantity || 1),
                confidence:         null,   // unknown on edit — renders "Saved Estimate" header
                aiReasoning:        null,
                benchmark:          null,
                breakdown:          null,   // aiRecalcPrice returns early; user can override price manually
                tags:               item.aiTags ? item.aiTags.split(',').filter(Boolean) : [],
                aiPredictionId:     item.aiPredictionId || null
            };
        }
    } else {
        wz.itemType = null;
    }

    updateWizardTitle();
    renderStep(1);
    updateWizardButtons();

    const modal = new bootstrap.Modal(document.getElementById('itemWizardModal'));
    modal.show();
}

function closeWizard() {
    bootstrap.Modal.getInstance(document.getElementById('itemWizardModal'))?.hide();
}

// ─── Step navigation ──────────────────────────────────────────────────────────

// Guard against Android ghost-clicks: after a touch tap the browser fires a
// synthetic click ~300ms later at the same screen coordinates. By then the
// Next button has been replaced by Save, so the ghost click saves the item
// before the user sees step 4. Disabling for 400ms covers the ghost-click window.
let _wizardNavBusy = false;
function _guardNav() {
    if (_wizardNavBusy) return false;
    _wizardNavBusy = true;
    setTimeout(() => { _wizardNavBusy = false; }, 400);
    return true;
}

function wizardNext() {
    if (!_guardNav()) return;
    if (!collectCurrentStep()) return; // validation failed

    if (wz.step === 1) {
        wz.step = 2;
    } else if (wz.step === 2) {
        // Labor / sales: step 2 is the last step
        if (wz.itemType === 'labor' || wz.itemType === 'sales') {
            wizardSave();
            return;
        }
        // Flat-Rate Charge: go to step 3 only if user opted in to specifying coatings
        if (wz.itemType === 'generic') {
            if (wz.data.genericHasCoatings) {
                wz.step = 3;
            } else {
                wizardSave();
                return;
            }
        }
        // AI: step 2 must be accepted before proceeding
        if (wz.itemType === 'ai') {
            if (!wz.ai.accepted) {
                document.getElementById('ai_acceptError')?.classList.remove('d-none');
                return; // don't advance
            }
        }
        wz.step = 3;
    } else if (wz.step === 3) {
        // Flat-Rate Charge with coatings stops here — no prep or catalog-save step
        if (wz.itemType === 'generic') {
            wizardSave();
            return;
        }
        // Step 4 (prep services) for product, calculated, and ai items — always shown
        // so users with no prep services configured see the empty-state prompt
        wz.step = 4;
    } else if (wz.step === 4) {
        // Calculated and AI items offer a Save-to-Catalog step before finalising
        if (wz.itemType === 'calculated' || wz.itemType === 'ai') {
            wz.step = 5;
        } else {
            wizardSave();
            return;
        }
    } else if (wz.step === 5) {
        // Step 5 actions are handled by saveToCatalogFromWizard() / skipCatalogSave()
        return;
    }

    renderStep(wz.step);
    updateWizardButtons();
    updateStepDots();
}

function wizardBack() {
    if (!_guardNav()) return;
    if (wz.step === 5) wz.step = 4;
    else if (wz.step === 4) wz.step = 3;
    else if (wz.step === 3) wz.step = 2;
    else if (wz.step === 2) wz.step = 1;

    renderStep(wz.step);
    updateWizardButtons();
    updateStepDots();
}

function wizardSave() {
    if (!_guardNav()) return;
    if (!collectCurrentStep()) return;

    const item = buildItemFromWizard();

    if (wz.editIndex >= 0) {
        quoteItems[wz.editIndex] = item;
    } else {
        quoteItems.push(item);
    }

    writeHiddenFields();
    renderAllCards();
    scheduleAutoPricing();
    closeWizard();
}

// ─── Step renderers ───────────────────────────────────────────────────────────

function renderStep(step) {
    wz.step = step;
    const body = document.getElementById('wizardBody');
    if (step === 1) body.innerHTML = renderStep1Html();
    else if (step === 2) body.innerHTML = renderStep2Html();
    else if (step === 3) body.innerHTML = renderStep3Html();
    else if (step === 4) body.innerHTML = renderStep4Html();
    else if (step === 5) {
        body.innerHTML = renderStep5Html();
        loadCatalogCategoriesForWizard();
        if (wz.itemType === 'calculated') prefillCatalogPriceFromCalc();
    }

    // Re-select the previously chosen type card
    if (step === 1 && wz.itemType) {
        document.querySelectorAll('.item-type-card').forEach(c => {
            c.classList.toggle('selected', c.dataset.type === wz.itemType);
        });
    }

    // Pre-fill step 2 fields from wz.data
    if (step === 2) preFillStep2();

    // Pre-fill step 3 coats
    if (step === 3) renderCoatsList();
}

// Step 1: Type picker
function renderStep1Html() {
    const types = [
        {
            type: 'product',
            icon: 'bi-bag-check',
            label: 'Product from Catalog',
            desc: 'Pick a pre-defined product. Dimensions and default pricing are already set up.'
        },
        {
            type: 'calculated',
            icon: 'bi-rulers',
            label: 'Custom / Measured Item',
            desc: 'Enter exact dimensions and coating time. Price is fully calculated by the engine.'
        },
        {
            type: 'generic',
            icon: 'bi-tag',
            label: 'Flat-Rate Charge',
            desc: 'Setup fees, handling, touch-ups — any charge with a fixed unit price.'
        },
        {
            type: 'labor',
            icon: 'bi-person-gear',
            label: 'Labor Only',
            desc: 'Billable hours at the shop labor rate. No coating required.'
        },
        {
            type: 'ai',
            icon: 'bi-robot',
            label: 'AI Photo Quote',
            desc: 'Upload a photo — our AI will analyze the item and estimate surface area, complexity, and price.'
        },
        {
            type: 'sales',
            icon: 'bi-shop',
            label: 'Retail / Merchandise',
            desc: 'Off-the-shelf items — T-shirts, tumblers, apparel, or any product sold at a fixed price.'
        },
        {
            type: 'formula',
            icon: 'bi-calculator',
            label: 'Custom Formula Item',
            desc: 'Use a saved formula template to price complex fabricated items (roof curbs, enclosures, frames).'
        }
    ].filter(t => {
        if (t.type === 'ai') return pageMeta.aiPhotoQuotesEnabled !== false;
        if (t.type === 'formula') return (pageMeta.customFormulaTemplates || []).length > 0;
        return true;
    });

    return `<div class="row g-3">` +
        types.map(t => `
            <div class="col-sm-6">
                <div class="item-type-card h-100${wz.itemType === t.type ? ' selected' : ''}"
                     data-type="${t.type}" onclick="selectItemType('${t.type}')">
                    <div class="item-type-icon"><i class="bi ${t.icon} text-primary"></i></div>
                    <div class="fw-semibold mb-1">${t.label}</div>
                    <small class="text-muted">${t.desc}</small>
                </div>
            </div>`
        ).join('') +
        `</div>
        <div id="typeError" class="text-danger mt-2 d-none">Please select an item type.</div>`;
}

function selectItemType(type) {
    wz.itemType = type;
    document.querySelectorAll('.item-type-card').forEach(c => {
        c.classList.toggle('selected', c.dataset.type === type);
    });
    document.getElementById('typeError')?.classList.add('d-none');
    updateStepDots();
    // Brief pause so the selected state is visible before advancing
    setTimeout(wizardNext, 180);
}

// Step 2: Item-type-specific fields
function renderStep2Html() {
    if (wz.itemType === 'product')    return renderProductFields();
    if (wz.itemType === 'calculated') return renderCalculatedFields();
    if (wz.itemType === 'generic')    return renderGenericFields();
    if (wz.itemType === 'labor')      return renderLaborFields();
    if (wz.itemType === 'ai')         return renderAiPhotoFields();
    if (wz.itemType === 'sales')      return renderSalesFields();
    if (wz.itemType === 'formula')    return renderFormulaFields();
    return '<p class="text-danger">Unknown item type.</p>';
}

function renderProductFields() {
    ensureCatalogPreviewEl();
    const catalogItems = catalogData.map(c => {
        const thumbHtml = c.thumbnailPath
            ? `<img src="/CatalogItems/Image?id=${c.value}&thumbnail=true" alt=""
                   style="width:36px;height:36px;object-fit:cover;border-radius:4px;flex-shrink:0;cursor:zoom-in;"
                   onmouseenter="showCatalogPreview(event,'/CatalogItems/Image?id=${c.value}&thumbnail=true')"
                   onmousemove="moveCatalogPreview(event)"
                   onmouseleave="hideCatalogPreview()" />`
            : `<span style="width:36px;height:36px;background:#f0f0f0;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;"><i class='bi bi-image text-muted' style='font-size:.85rem;'></i></span>`;
        // Inner div carries the flex layout — the outer catalog-list-item div must stay a plain block element
        // so filterCatalog() can set el.style.display='none' without Bootstrap d-flex !important overriding it.
        return `<div class="catalog-list-item px-2 py-2" data-value="${c.value}" onclick="pickCatalogItem(this)"><div style="display:flex;align-items:center;gap:0.5rem;">${thumbHtml}<span>${escHtml(c.text)}</span></div></div>`;
    }).join('');

    return `
        <div class="mb-3">
            <label class="form-label fw-semibold">Search / Filter Products</label>
            <input type="text" class="form-control mb-2" id="catalogSearch" placeholder="Type to filter…" oninput="filterCatalog()" autocomplete="off">
            <label class="form-label fw-semibold">Product <span class="text-danger">*</span></label>
            <input type="hidden" id="wz_catalogItemId">
            <div class="border rounded" id="catalogListbox" style="max-height:220px;overflow-y:auto;">
                ${catalogItems || '<div class="px-3 py-2 text-muted small">No catalog items found.</div>'}
            </div>
            <div class="text-danger d-none mt-1" id="err_catalogItemId">Please select a product.</div>
        </div>
        <div class="row g-3">
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Quantity <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_quantity" min="0.01" step="0.01" value="1">
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Price Override <small class="text-muted">($/unit, optional)</small></label>
                <div class="input-group">
                    <span class="input-group-text">$</span>
                    <input type="number" class="form-control" id="wz_powderCostOverride" min="0" step="0.01" placeholder="Leave blank to use catalog price">
                </div>
            </div>
        </div>`;
}

function filterCatalog() {
    const q     = document.getElementById('catalogSearch').value.toLowerCase();
    const words = q.split(/\s+/).filter(Boolean);
    const listbox = document.getElementById('catalogListbox');
    if (!listbox) return;
    listbox.querySelectorAll('.catalog-list-item').forEach(el => {
        const text = el.textContent.toLowerCase();
        const hide = words.length > 0 && !words.every(w => text.includes(w));
        el.style.display = hide ? 'none' : '';
    });
    // Reset scroll so filtered-in items are always visible at the top.
    listbox.scrollTop = 0;
    // Force a layout recalculation — required on iOS Safari so elements that
    // transition from display:none back to visible register as interactive.
    // eslint-disable-next-line no-unused-expressions
    listbox.offsetHeight;
}

function pickCatalogItem(el) {
    document.querySelectorAll('#catalogListbox .catalog-list-item').forEach(e => e.classList.remove('selected'));
    el.classList.add('selected');
    document.getElementById('wz_catalogItemId').value = el.dataset.value;
    document.getElementById('err_catalogItemId')?.classList.add('d-none');
}

// ── Catalog thumbnail hover preview ──────────────────────────────────────────

function ensureCatalogPreviewEl() {
    if (document.getElementById('catalogThumbPreview')) return;
    const el = document.createElement('div');
    el.id = 'catalogThumbPreview';
    el.style.cssText = 'position:fixed;display:none;z-index:9999;pointer-events:none;' +
        'border:1px solid #dee2e6;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,0.18);' +
        'background:#fff;padding:4px;';
    el.innerHTML = '<img id="catalogThumbPreviewImg" style="display:block;width:200px;height:200px;object-fit:contain;border-radius:4px;" />';
    document.body.appendChild(el);
}

function showCatalogPreview(event, url) {
    const preview = document.getElementById('catalogThumbPreview');
    const img    = document.getElementById('catalogThumbPreviewImg');
    if (!preview || !img) return;
    img.src = url;
    _placeCatalogPreview(event, preview);
    preview.style.display = 'block';
}

function moveCatalogPreview(event) {
    const preview = document.getElementById('catalogThumbPreview');
    if (preview && preview.style.display !== 'none') _placeCatalogPreview(event, preview);
}

function hideCatalogPreview() {
    const preview = document.getElementById('catalogThumbPreview');
    if (preview) preview.style.display = 'none';
}

function _placeCatalogPreview(event, preview) {
    const pad = 16, pw = 216, ph = 216;
    let x = event.clientX + pad;
    let y = event.clientY - ph / 2;
    if (x + pw > window.innerWidth)  x = event.clientX - pw - pad;
    if (y < 8)                       y = 8;
    if (y + ph > window.innerHeight) y = window.innerHeight - ph - 8;
    preview.style.left = x + 'px';
    preview.style.top  = y + 'px';
}

function renderCalculatedFields() {
    const areaUnit = pageMeta.areaUnit || 'sq ft';
    return `
        <div class="row g-3">
            <div class="col-12">
                <label class="form-label fw-semibold">Description <span class="text-danger">*</span></label>
                <input type="text" class="form-control" id="wz_description" placeholder="e.g., Steel bracket, aluminium frame…" maxlength="200">
                <div class="text-danger d-none mt-1" id="err_description">Description is required.</div>
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Quantity <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_quantity" min="0.01" step="0.01" value="1">
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">${escHtml(areaUnit)} per item <span class="text-danger">*</span></label>
                <div class="input-group">
                    <input type="number" class="form-control" id="wz_surfaceAreaSqFt" min="0.01" step="0.01" placeholder="0.00">
                    <button type="button" class="btn btn-outline-secondary" onclick="wzToggleCalc()" title="Area calculator">
                        <i class="bi bi-calculator"></i>
                    </button>
                </div>
                <div class="text-danger d-none mt-1" id="err_surfaceAreaSqFt">Surface area is required.</div>
                <!-- Inline area calculator (avoids modal-on-modal) -->
                <div id="wz_calcPanel" class="d-none mt-2 p-3 border rounded bg-light" style="font-size:.875rem;">
                    <div class="fw-semibold mb-2">Area Calculator</div>
                    <div class="mb-2">
                        <select class="form-select form-select-sm" id="wz_calcShape" onchange="wzCalcShape()">
                            <option value="rectangle">Rectangle / Flat panel</option>
                            <option value="cylinder">Cylinder</option>
                            <option value="circle">Circle / Disc</option>
                        </select>
                    </div>
                    <div id="wz_calcRect">
                        <div class="row g-2 mb-2">
                            <div class="col-6">
                                <label class="form-label form-label-sm mb-1">Length (in)</label>
                                <input type="number" class="form-control form-control-sm" id="wz_calcL" min="0" step="0.1" value="0" oninput="wzCalcCompute()">
                            </div>
                            <div class="col-6">
                                <label class="form-label form-label-sm mb-1">Width (in)</label>
                                <input type="number" class="form-control form-control-sm" id="wz_calcW" min="0" step="0.1" value="0" oninput="wzCalcCompute()">
                            </div>
                        </div>
                    </div>
                    <div id="wz_calcCyl" class="d-none">
                        <div class="row g-2 mb-2">
                            <div class="col-6">
                                <label class="form-label form-label-sm mb-1">Diameter (in)</label>
                                <input type="number" class="form-control form-control-sm" id="wz_calcD" min="0" step="0.1" value="0" oninput="wzCalcCompute()">
                            </div>
                            <div class="col-6">
                                <label class="form-label form-label-sm mb-1">Height (in)</label>
                                <input type="number" class="form-control form-control-sm" id="wz_calcH" min="0" step="0.1" value="0" oninput="wzCalcCompute()">
                            </div>
                        </div>
                    </div>
                    <div id="wz_calcCirc" class="d-none">
                        <div class="row g-2 mb-2">
                            <div class="col-6">
                                <label class="form-label form-label-sm mb-1">Diameter (in)</label>
                                <input type="number" class="form-control form-control-sm" id="wz_calcCD" min="0" step="0.1" value="0" oninput="wzCalcCompute()">
                            </div>
                        </div>
                    </div>
                    <div class="d-flex align-items-center justify-content-between mt-1">
                        <span class="text-muted">Result: <strong id="wz_calcResult">0.000</strong> ${escHtml(areaUnit)}</span>
                        <div class="d-flex gap-2">
                            <button type="button" class="btn btn-sm btn-outline-secondary" onclick="document.getElementById('wz_calcPanel').classList.add('d-none')">Cancel</button>
                            <button type="button" class="btn btn-sm btn-primary" onclick="wzCalcApply()">Apply</button>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Part Complexity</label>
                <select class="form-select" id="wz_complexity">
                    <option value="Simple">Simple</option>
                    <option value="Moderate">Moderate</option>
                    <option value="Complex">Complex</option>
                    <option value="Extreme">Extreme</option>
                </select>
                <small class="text-muted">Price multiplier for part intricacy</small>
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Coating time <small class="text-muted">(min/item)</small> <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_estimatedMinutes" min="1" value="30">
            </div>
        </div>`;
}

function renderGenericFields() {
    return `
        <div class="row g-3">
            <div class="col-12">
                <label class="form-label fw-semibold">Description <span class="text-danger">*</span></label>
                <input type="text" class="form-control" id="wz_description" placeholder="e.g., Setup charge, Touch-up fee, Handling…" maxlength="200">
                <div class="text-danger d-none mt-1" id="err_description">Description is required.</div>
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Quantity <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_quantity" min="0.01" step="0.01" value="1">
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Unit Price <span class="text-danger">*</span></label>
                <div class="input-group">
                    <span class="input-group-text">$</span>
                    <input type="number" class="form-control" id="wz_manualUnitPrice" min="0" step="0.01" placeholder="0.00">
                </div>
                <div class="text-danger d-none mt-1" id="err_manualUnitPrice">Unit price is required.</div>
            </div>
            <div class="col-12">
                <label class="form-label fw-semibold">Notes <small class="text-muted">(optional)</small></label>
                <input type="text" class="form-control" id="wz_notes" placeholder="Internal notes about this charge…" maxlength="500">
            </div>
        </div>
        <div class="d-flex align-items-center border rounded py-2 px-3 mt-3 gap-2">
            <div class="form-check form-switch mb-0 flex-shrink-0">
                <input class="form-check-input" type="checkbox" id="wz_genericHasCoatings"
                       ${wz.data.genericHasCoatings ? 'checked' : ''}
                       onchange="wz.data.genericHasCoatings = this.checked; updateStepDots(); updateWizardButtons();"
                       style="cursor:pointer">
            </div>
            <label for="wz_genericHasCoatings" class="mb-0" style="cursor:pointer; line-height:1.3">
                <strong>Specify powder coating</strong>
                <span class="d-block text-muted fw-normal small">Add coat &amp; color specs for powder ordering and tracking (price stays flat)</span>
            </label>
        </div>`;
}

function renderLaborFields() {
    return `
        <div class="row g-3">
            <div class="col-12">
                <label class="form-label fw-semibold">Description <span class="text-danger">*</span></label>
                <input type="text" class="form-control" id="wz_description" placeholder="e.g., Sandblasting prep, Custom masking, Assembly…" maxlength="200">
                <div class="text-danger d-none mt-1" id="err_description">Description is required.</div>
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Billable Hours <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_quantity" min="0.25" step="0.25" value="1">
                <small class="text-muted">Minimum 0.25 hr (15 min)</small>
            </div>
            <div class="col-12">
                <label class="form-label fw-semibold">Notes <small class="text-muted">(optional)</small></label>
                <input type="text" class="form-control" id="wz_notes" placeholder="What does this labor cover?" maxlength="500">
            </div>
        </div>`;
}

function renderSalesFields() {
    if (merchandiseData.length === 0) {
        return `<div class="alert alert-warning">
            <i class="bi bi-exclamation-triangle me-2"></i>
            No merchandise items are set up yet. Go to <strong>Catalog Items</strong> and mark items as merchandise to make them available here.
        </div>`;
    }

    // Resolve display name for the currently selected item (when editing)
    const selectedItem = wz.data.salesCatalogItemId
        ? merchandiseData.find(m => m.id === wz.data.salesCatalogItemId) : null;

    return `
        <div class="mb-3">
            <label class="form-label fw-semibold">Item <span class="text-danger">*</span></label>
            <div class="position-relative" id="wzMerchComboWrapper">
                <div class="input-group">
                    <input type="text" id="wzMerchInput" class="form-control"
                           placeholder="Search merchandise…" autocomplete="off"
                           value="${escHtml(selectedItem ? selectedItem.name + (selectedItem.sku ? ' [' + selectedItem.sku + ']' : '') : '')}"
                           oninput="wzMerchComboInput()" onfocus="wzMerchComboOpen()"
                           onkeydown="wzMerchComboKey(event)" />
                    <button class="btn btn-outline-secondary" type="button" tabindex="-1" onclick="wzMerchComboToggle()">
                        <i class="bi bi-chevron-down" style="font-size:.75rem;"></i>
                    </button>
                </div>
                <div id="wzMerchDropdown"
                     style="display:none;max-height:220px;overflow-y:auto;z-index:1060;background:var(--bs-body-bg);color:var(--bs-body-color);border:1px solid var(--bs-border-color);border-radius:0.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);position:fixed;">
                </div>
            </div>
            <div class="text-danger d-none mt-1" id="err_salesCatalogItemId">Please select an item.</div>
        </div>
        <div class="row g-3" id="merch_details" style="${selectedItem ? '' : 'display:none'}">
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Quantity <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="wz_quantity" min="1" step="1" value="${wz.data.quantity || 1}">
            </div>
            <div class="col-sm-4">
                <label class="form-label fw-semibold">Unit Price <span class="text-danger">*</span></label>
                <div class="input-group">
                    <span class="input-group-text">$</span>
                    <input type="number" class="form-control" id="wz_manualUnitPrice" min="0" step="0.01"
                           placeholder="0.00"
                           value="${wz.data.manualUnitPrice != null ? wz.data.manualUnitPrice : ''}">
                </div>
                <div class="text-danger d-none mt-1" id="err_manualUnitPrice">A valid unit price is required.</div>
            </div>
            <div class="col-12">
                <label class="form-label fw-semibold">Notes <small class="text-muted">(optional)</small></label>
                <input type="text" class="form-control" id="wz_notes"
                       placeholder="Size, color variant, any special notes…" maxlength="500"
                       value="${escHtml(wz.data.notes || '')}">
            </div>
        </div>`;
}

// ── Merchandise combobox functions (wizard context) ───────────────────────────

function wzMerchComboInput() {
    wz.data.salesCatalogItemId = null; // clear selection while typing
    const q = document.getElementById('wzMerchInput')?.value.toLowerCase() || '';
    wzMerchComboRender(q);
    wzMerchComboShow();
}

function wzMerchComboOpen() {
    const q = document.getElementById('wzMerchInput')?.value.toLowerCase() || '';
    wzMerchComboRender(q);
    wzMerchComboShow();
}

function wzMerchComboToggle() {
    const dd = document.getElementById('wzMerchDropdown');
    if (!dd) return;
    if (dd.style.display === 'none') {
        document.getElementById('wzMerchInput')?.focus();
        wzMerchComboOpen();
    } else {
        wzMerchComboClose();
    }
}

function wzMerchComboRender(query) {
    const dd = document.getElementById('wzMerchDropdown');
    if (!dd) return;
    const filtered = query
        ? merchandiseData.filter(m =>
            m.name.toLowerCase().includes(query) ||
            (m.sku && m.sku.toLowerCase().includes(query)) ||
            m.category.toLowerCase().includes(query))
        : merchandiseData;

    if (filtered.length === 0) {
        dd.innerHTML = '<div class="px-3 py-2 text-muted small">No items match your search</div>';
        return;
    }

    const groups = {};
    filtered.forEach(m => {
        if (!groups[m.category]) groups[m.category] = [];
        groups[m.category].push(m);
    });

    dd.innerHTML = Object.keys(groups).sort().map(cat =>
        `<div class="px-3 pt-2 pb-1" style="font-size:.75rem;font-weight:600;color:#6c757d;text-transform:uppercase;letter-spacing:.05em;">${escHtml(cat)}</div>` +
        groups[cat].map(m =>
            `<div class="wz-merch-opt" style="padding:.35rem .75rem .35rem 1.25rem;font-size:.875rem;cursor:pointer;"
                  data-id="${m.id}" data-name="${escHtml(m.name)}" data-price="${m.price}" data-sku="${escHtml(m.sku || '')}"
                  onmousedown="event.preventDefault();wzMerchComboSelect(this)"
                  onmouseenter="this.style.background='var(--bs-secondary-bg)'"
                  onmouseleave="this.style.background=''">
                ${escHtml(m.name)}${m.sku ? ` <span class="text-muted">[${escHtml(m.sku)}]</span>` : ''} <span class="text-muted">— $${parseFloat(m.price).toFixed(2)}</span>
             </div>`
        ).join('')
    ).join('');
}

function wzMerchComboShow() {
    const dd = document.getElementById('wzMerchDropdown');
    const anchor = document.getElementById('wzMerchInput');
    if (!dd || !anchor) return;
    const rect = anchor.closest('.input-group').getBoundingClientRect();
    dd.style.top   = (rect.bottom + 2) + 'px';
    dd.style.left  = rect.left + 'px';
    dd.style.width = rect.width + 'px';
    dd.style.display = 'block';
}

function wzMerchComboClose() {
    const dd = document.getElementById('wzMerchDropdown');
    if (dd) dd.style.display = 'none';
}

function wzMerchComboSelect(el) {
    const id    = parseInt(el.dataset.id);
    const name  = el.dataset.name;
    const price = parseFloat(el.dataset.price);
    const sku   = el.dataset.sku;

    wz.data.salesCatalogItemId = id;

    document.getElementById('wzMerchInput').value = name + (sku ? ` [${sku}]` : '');
    wzMerchComboClose();

    // Show details panel and auto-fill price
    const details = document.getElementById('merch_details');
    if (details) details.style.display = '';
    const priceInput = document.getElementById('wz_manualUnitPrice');
    if (priceInput && (!priceInput.value || parseFloat(priceInput.value) === 0)) {
        priceInput.value = price.toFixed(2);
    }
    document.getElementById('err_salesCatalogItemId')?.classList.add('d-none');
    document.getElementById('wz_quantity')?.focus();
}

function wzMerchComboKey(e) {
    const dd = document.getElementById('wzMerchDropdown');
    if (!dd || dd.style.display === 'none') return;
    const opts = Array.from(dd.querySelectorAll('.wz-merch-opt'));
    const active = dd.querySelector('.wz-merch-opt.mc-active');
    let idx = opts.indexOf(active);
    if (e.key === 'ArrowDown') {
        e.preventDefault();
        if (active) { active.classList.remove('mc-active'); active.style.background = ''; }
        idx = (idx + 1) % opts.length;
        opts[idx].classList.add('mc-active'); opts[idx].style.background = '#f0f4ff';
        opts[idx].scrollIntoView({ block: 'nearest' });
    } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        if (active) { active.classList.remove('mc-active'); active.style.background = ''; }
        idx = (idx - 1 + opts.length) % opts.length;
        opts[idx].classList.add('mc-active'); opts[idx].style.background = '#f0f4ff';
        opts[idx].scrollIntoView({ block: 'nearest' });
    } else if (e.key === 'Enter' && active) {
        e.preventDefault();
        wzMerchComboSelect(active);
    } else if (e.key === 'Escape') {
        wzMerchComboClose();
    }
}

// Close dropdown when clicking outside the wizard combobox
document.addEventListener('mousedown', e => {
    const wrapper = document.getElementById('wzMerchComboWrapper');
    const dd = document.getElementById('wzMerchDropdown');
    if (wrapper && dd && !wrapper.contains(e.target) && !dd.contains(e.target)) {
        wzMerchComboClose();
    }
});

// ─── Formula Item Fields ──────────────────────────────────────────────────────

function renderFormulaFields() {
    const templates = pageMeta.customFormulaTemplates || [];
    if (!templates.length) return '<p class="text-warning">No formula templates found. Create one in Company Settings &rarr; Custom Formulas.</p>';

    const selectedId = wz.data.customItemTemplateId || null;
    const selected = templates.find(t => t.id === selectedId) || null;

    // Parse stored field values if re-editing
    let storedValues = {};
    try { storedValues = JSON.parse(wz.data.formulaFieldValuesJson || '{}'); } catch { storedValues = {}; }

    const templateOptions = templates.map(t =>
        `<option value="${t.id}" ${t.id === selectedId ? 'selected' : ''}>${escHtml(t.name)}</option>`
    ).join('');

    let fieldsHtml = '';
    if (selected) {
        let fields = [];
        try { fields = JSON.parse(selected.fieldsJson || '[]'); } catch { fields = []; }

        const diagramHtml = selected.diagramImagePath
            ? `<div class="mb-3 text-center">
                   <img src="${escHtml(selected.diagramImagePath)}" alt="Template diagram"
                        class="img-fluid rounded border" style="max-height:160px;" />
                   <div class="form-text">Reference diagram for this template</div>
               </div>`
            : '';

        const rateField = selected.outputMode === 'FixedRate' && selected.defaultRate != null
            ? `<div class="row g-2 mb-2 align-items-center">
                   <div class="col-5"><label class="form-label mb-0">${escHtml(selected.rateLabel || 'Rate')}</label></div>
                   <div class="col-5"><input type="number" id="wz_formula_rate" class="form-control form-control-sm"
                         step="0.01" value="${storedValues['rate'] ?? selected.defaultRate}" /></div>
                   <div class="col-2 text-muted small">${escHtml(selected.rateLabel || '')}</div>
               </div>`
            : '';

        fieldsHtml = `
            ${diagramHtml}
            ${rateField}
            ${fields.map(f => `
            <div class="row g-2 mb-2 align-items-center">
                <div class="col-5"><label class="form-label mb-0">${escHtml(f.label || f.name)}</label></div>
                <div class="col-5">
                    <input type="number" id="wz_fld_${escHtml(f.name)}" class="form-control form-control-sm"
                           step="any" value="${storedValues[f.name] ?? f.defaultValue ?? ''}"
                           onchange="wzFormulaRecalc()" />
                </div>
                <div class="col-2 text-muted small">${escHtml(f.unit || '')}</div>
            </div>`).join('')}
            <div class="mt-3 p-2 border rounded bg-light d-flex align-items-center gap-3">
                <button type="button" class="btn btn-sm btn-outline-primary" onclick="wzFormulaRecalc()">
                    <i class="bi bi-calculator"></i> Calculate
                </button>
                <span id="wz_formula_result" class="fw-bold fs-5"></span>
                <span class="text-muted small">${selected.outputMode === 'FixedRate' ? 'total price' : 'sq ft'}</span>
            </div>`;
    }

    return `
        <div class="mb-3">
            <label class="form-label">Formula Template <span class="text-danger">*</span></label>
            <select id="wz_formulaTemplate" class="form-select" onchange="wzFormulaTemplateChanged()">
                <option value="">-- Select a template --</option>
                ${templateOptions}
            </select>
            <div id="err_formulaTemplate" class="text-danger small mt-1 d-none">Please select a template.</div>
        </div>
        <div class="mb-2">
            <label class="form-label">Description</label>
            <input type="text" id="wz_formula_description" class="form-control"
                   value="${escHtml(wz.data.description || (selected ? selected.name : ''))}" />
        </div>
        <div class="mb-2 row g-2">
            <div class="col-4">
                <label class="form-label">Quantity</label>
                <input type="number" id="wz_formula_qty" class="form-control" value="${wz.data.quantity || 1}" min="0.01" step="0.01" />
            </div>
        </div>
        <div id="wz_formula_fields">${fieldsHtml}</div>
        <div id="err_formulaCalc" class="text-danger small mt-1 d-none">Please calculate the formula first.</div>
        <div class="form-text mt-2 text-muted" style="font-size:0.75rem;">
            <i class="bi bi-info-circle me-1"></i>Shop rates automatically applied:
            <code class="ms-1">standard_labor_rate</code>
            <code class="ms-1">additional_coat_labor_pct</code>
            <code class="ms-1">markup_pct</code>
        </div>`;
}

window.wzFormulaTemplateChanged = function () {
    const id = parseInt(document.getElementById('wz_formulaTemplate')?.value);
    wz.data.customItemTemplateId = isNaN(id) ? null : id;
    wz.data.formulaResult = null;
    document.getElementById('wz_formula_fields').innerHTML = renderFormulaFieldInputs();
};

function renderFormulaFieldInputs() {
    const templates = pageMeta.customFormulaTemplates || [];
    const selected = templates.find(t => t.id === wz.data.customItemTemplateId);
    if (!selected) return '';
    // Re-render the inner fields portion
    const tmp = renderFormulaFields();
    // Extract just the fields section by re-rendering with selected
    const div = document.createElement('div');
    div.innerHTML = tmp;
    return div.querySelector('#wz_formula_fields')?.innerHTML || '';
}

window.wzFormulaRecalc = async function () {
    const templates = pageMeta.customFormulaTemplates || [];
    const selected = templates.find(t => t.id === wz.data.customItemTemplateId);
    if (!selected) return;

    let fields = [];
    try { fields = JSON.parse(selected.fieldsJson || '[]'); } catch { fields = []; }

    const variables = {};
    fields.forEach(f => {
        const el = document.getElementById(`wz_fld_${f.name}`);
        variables[f.name] = el ? (parseFloat(el.value) || 0) : (f.defaultValue || 0);
    });
    const rateEl = document.getElementById('wz_formula_rate');
    if (rateEl) variables['rate'] = parseFloat(rateEl.value) || 0;

    try {
        const res = await fetch(pageMeta.formulaEvalUrl || '/CompanySettings/EvaluateFormula', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': getRequestVerificationToken() },
            body: JSON.stringify({ formula: selected.formula, variablesJson: JSON.stringify(variables) })
        });
        const data = await res.json();
        const el = document.getElementById('wz_formula_result');
        if (data.success && el) {
            el.textContent = selected.outputMode === 'FixedRate'
                ? `$${Number(data.result).toFixed(2)}`
                : `${Number(data.result).toFixed(3)} sq ft`;
            el.className = 'fw-bold fs-5 text-success';
            wz.data.formulaResult = data.result;
            wz.data.formulaVariables = variables;
        } else if (el) {
            el.textContent = data.error || 'Error';
            el.className = 'fw-bold text-danger';
        }
    } catch (e) {
        const el = document.getElementById('wz_formula_result');
        if (el) { el.textContent = 'Request failed'; el.className = 'fw-bold text-danger'; }
    }
};

function getRequestVerificationToken() {
    return document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
}

function renderAiPhotoFields() {
    const existingPhotoHtml = wz.ai.tempIds.map((tid, i) => {
        const previewUrl = wz.ai.previewUrls[i] || '';
        const thumb = previewUrl
            ? `<img src="${escHtml(previewUrl)}" alt="" style="width:48px;height:48px;object-fit:cover;border-radius:4px;flex-shrink:0;">`
            : `<i class="bi bi-image text-primary fs-5"></i>`;
        return `
        <div class="d-flex align-items-center gap-2 p-2 border rounded bg-light mb-1" id="ai_photoRow_${i}">
            ${thumb}
            <span class="flex-grow-1 small text-truncate">${escHtml(wz.ai.fileNames[i] || tid)}</span>
            <button type="button" class="btn btn-sm btn-outline-danger" onclick="aiRemovePhoto(${i})"><i class="bi bi-x"></i></button>
        </div>`;
    }).join('');

    const resultHtml = wz.ai.accepted && wz.ai.result ? renderAiResultHtml(wz.ai.result) : '';

    return `
        <!-- Photo Upload Section -->
        <div class="mb-3">
            <label class="form-label fw-semibold">Photos <span class="text-danger">*</span>
                <small class="text-muted fw-normal">(jpg, png, webp — max 10 MB each)</small>
            </label>
            <div id="ai_dropzone" class="border border-2 border-dashed rounded p-4 text-center text-muted mb-2"
                 style="cursor:pointer;border-style:dashed!important;min-height:80px;"
                 onclick="document.getElementById('ai_fileInput').click()"
                 ondragover="event.preventDefault();this.classList.add('bg-primary','bg-opacity-10')"
                 ondragleave="this.classList.remove('bg-primary','bg-opacity-10')"
                 ondrop="event.preventDefault();this.classList.remove('bg-primary','bg-opacity-10');aiHandleDrop(event)">
                <i class="bi bi-cloud-upload fs-3 d-block mb-1"></i>
                <span>Click to browse or drag &amp; drop photos here</span>
            </div>
            <input type="file" id="ai_fileInput" class="d-none" accept="image/*" multiple onchange="aiHandleFileSelect(this)">
            <div id="ai_photoList">${existingPhotoHtml}</div>
            <div class="text-danger d-none mt-1" id="ai_photoError">Please upload at least one photo.</div>
        </div>

        <!-- Metadata Inputs -->
        <div class="row g-3 mb-3">
            <div class="col-12">
                <label class="form-label fw-semibold">Item Description &amp; Known Dimension <span class="text-danger">*</span>
                    <small class="text-muted fw-normal">Describe the item and include at least one known measurement so the AI can estimate surface area</small>
                </label>
                <input type="text" class="form-control" id="ai_referenceDim"
                       placeholder='e.g. "18-inch aluminum wheel", "steel bracket approx 24 × 12 inches", "truck bumper about 60 inches wide"'
                       value="${escHtml(wz.data.aiReferenceDim || '')}">
                <div class="text-danger d-none mt-1" id="ai_dimError">Item description with dimension is required.</div>
            </div>
            <div class="col-sm-3">
                <label class="form-label fw-semibold">Quantity <span class="text-danger">*</span></label>
                <input type="number" class="form-control" id="ai_quantity" min="0.01" step="0.01" value="${wz.data.quantity || 1}">
            </div>
            <div class="col-sm-3">
                <label class="form-label fw-semibold">Desired Color / Finish</label>
                <input type="text" class="form-control" id="ai_color"
                       placeholder="e.g., Gloss Black, RAL 7016…"
                       value="${escHtml(wz.data.aiColor || '')}">
            </div>
            <div class="col-sm-3">
                <label class="form-label fw-semibold">Coating Stages</label>
                <input type="number" class="form-control" id="ai_coatCount" min="1" max="5" value="${wz.data.aiCoatCount || 1}">
                <small class="text-muted">Number of powder coats to apply</small>
            </div>
            <div class="col-sm-3">
                <label class="form-label fw-semibold">Est. Weight (lbs)</label>
                <input type="number" class="form-control" id="ai_weightLbs" min="0" step="0.5"
                       placeholder="Optional"
                       value="${wz.data.aiWeightLbs || ''}">
                <small class="text-muted">Per piece — helps price heavy items</small>
            </div>
        </div>
        <div class="row g-3 mb-3">
            <div class="col-sm-6">
                <label class="form-label fw-semibold">Material Type</label>
                <select class="form-select" id="ai_materialType">
                    <option value="" ${!wz.data.aiMaterialType ? 'selected' : ''}>Unknown / Let AI decide</option>
                    <option value="Cast Iron" ${'Cast Iron' === wz.data.aiMaterialType ? 'selected' : ''}>Cast Iron</option>
                    <option value="Cast Aluminum" ${'Cast Aluminum' === wz.data.aiMaterialType ? 'selected' : ''}>Cast Aluminum</option>
                    <option value="Steel (sheet/tube)" ${'Steel (sheet/tube)' === wz.data.aiMaterialType ? 'selected' : ''}>Steel (sheet / tube)</option>
                    <option value="Heavy Steel (structural/plate)" ${'Heavy Steel (structural/plate)' === wz.data.aiMaterialType ? 'selected' : ''}>Heavy Steel (structural / plate)</option>
                    <option value="Aluminum (sheet/extrusion)" ${'Aluminum (sheet/extrusion)' === wz.data.aiMaterialType ? 'selected' : ''}>Aluminum (sheet / extrusion)</option>
                    <option value="Stainless Steel" ${'Stainless Steel' === wz.data.aiMaterialType ? 'selected' : ''}>Stainless Steel</option>
                    <option value="Galvanized Steel" ${'Galvanized Steel' === wz.data.aiMaterialType ? 'selected' : ''}>Galvanized Steel</option>
                    <option value="Wrought Iron" ${'Wrought Iron' === wz.data.aiMaterialType ? 'selected' : ''}>Wrought Iron</option>
                    <option value="Other" ${'Other' === wz.data.aiMaterialType ? 'selected' : ''}>Other</option>
                </select>
                <small class="text-muted">Affects prep, outgassing, cure time</small>
            </div>
        </div>

        <!-- Blast Setup Selector (shown only when 2+ setups defined) -->
        ${blastSetupData.length > 1 ? `
        <div class="mb-3">
            <label for="ai_blastSetupId" class="form-label fw-medium">
                <i class="bi bi-wind me-1"></i>Blast Setup
                <small class="text-muted fw-normal ms-1">— which rig will be used?</small>
            </label>
            <select class="form-select" id="ai_blastSetupId">
                ${blastSetupData.map(s => `<option value="${s.id}" ${s.isDefault ? 'selected' : ''}>${escHtml(s.name)}</option>`).join('')}
            </select>
        </div>` : ''}

        <!-- Analyze Button -->
        <div class="d-flex align-items-center gap-2 mb-3">
            <button type="button" class="btn btn-primary" onclick="aiAnalyze()" id="ai_analyzeBtn">
                <i class="bi bi-robot me-1"></i>Analyze with AI
            </button>
            <div id="ai_loadingSpinner" class="d-none spinner-border spinner-border-sm text-primary" role="status"></div>
            <span id="ai_loadingText" class="d-none text-muted small">Analyzing photos, please wait…</span>
        </div>

        <!-- Follow-up Question Section -->
        <div id="ai_followupSection" class="${wz.ai.phase === 'followup' ? '' : 'd-none'} alert alert-info">
            <div class="fw-semibold mb-2"><i class="bi bi-question-circle me-1"></i>AI Follow-up Question:</div>
            <p id="ai_followupQuestion" class="mb-2">${escHtml(wz.ai.followUpQuestion || '')}</p>
            <div class="d-flex gap-2">
                <input type="text" class="form-control" id="ai_followupAnswer" placeholder="Your answer…">
                <button type="button" class="btn btn-primary btn-sm text-nowrap" onclick="aiSendFollowup()">Send Answer</button>
            </div>
        </div>

        <!-- AI Error -->
        <div id="ai_errorAlert" class="d-none alert alert-danger"></div>

        <!-- Results Section -->
        <div id="ai_resultsSection" class="${wz.ai.phase === 'result' ? '' : 'd-none'}">
            ${resultHtml}
        </div>

        <!-- Accept Error -->
        <div class="text-danger d-none mt-2" id="ai_acceptError">Please analyze your photos and accept the AI estimate before continuing.</div>`;
}

// Recompute AI unit price when the user changes sqft, minutes, or complexity.
// Debounced — calls the server so no pricing rates are exposed client-side.
let _recalcDebounce = null;
function aiRecalcPrice() {
    clearTimeout(_recalcDebounce);
    _recalcDebounce = setTimeout(_aiRecalcPriceAsync, 300);
}

async function _aiRecalcPriceAsync() {
    const b = wz.ai.result?.breakdown;
    if (!b) return;

    const sqft       = parseFloat(document.getElementById('ai_sqftOverride')?.value) || b.surfaceAreaSqFt;
    const minutes    = parseInt(document.getElementById('ai_minutesOverride')?.value)  || b.estimatedMinutes;
    const complexity = document.getElementById('ai_complexityOverride')?.value         || b.complexity;
    // Use the actual step-3 coat count so the price preview reflects whatever the user
    // added in the coating layers screen, not the coat count fixed at AI analysis time.
    const coatCount  = (wz.data.coats || []).length || b.coatCount || 1;

    const recalcUrl = pageMeta.aiRecalcUrl || '/Quotes/AiRecalcPrice';
    const csrf = document.querySelector('input[name="__RequestVerificationToken"]')?.value || '';

    try {
        const resp = await fetch(recalcUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': csrf },
            body: JSON.stringify({ surfaceAreaSqFt: sqft, estimatedMinutes: minutes, complexity, coatCount })
        });
        const result = await resp.json();
        if (!result.success) return;

        const unitPrice = result.unitPrice;

        const priceOverrideEl = document.getElementById('ai_priceOverride');
        if (priceOverrideEl) priceOverrideEl.placeholder = unitPrice.toFixed(2);

        const priceDisplayEl = document.getElementById('ai_priceDisplay');
        if (priceDisplayEl) {
            const fmt = v => '$' + v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
            priceDisplayEl.textContent = fmt(unitPrice);
        }

        // Store so Accept uses the recalculated price when no manual override is entered
        wz.ai.recalcUnitPrice = unitPrice;
    } catch (_) {
        // Silent fail — user can still enter a manual price override
    }
}

function buildAiPriceBreakdown(result) {
    const b = result.breakdown;
    if (!b) return '';
    const fmt = v => '$' + (v || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    return `<details class="mb-2">
        <summary class="text-muted small fw-semibold" style="cursor:pointer">
            <i class="bi bi-calculator me-1"></i>Price Breakdown <span class="text-muted fw-normal">(click to expand)</span>
        </summary>
        <table class="table table-sm table-borderless mb-0 mt-2" style="font-size:.82rem;">
            <tbody>
                <tr class="table-light">
                    <td colspan="2" class="fw-semibold text-muted py-1 px-2">Materials</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Surface area</td>
                    <td class="text-end">${b.surfaceAreaSqFt.toFixed(2)} sq ft</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Powder (${b.powderLbsPerCoat.toFixed(3)} lb/coat × ${b.coatCount} coat${b.coatCount !== 1 ? 's' : ''})</td>
                    <td class="text-end">${fmt(b.materialCost)}</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Consumables</td>
                    <td class="text-end">${fmt(b.consumablesCost)}</td>
                </tr>
                <tr class="table-light">
                    <td colspan="2" class="fw-semibold text-muted py-1 px-2">Labor &amp; Equipment</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Active labor (${b.estimatedMinutes} min)${b.minFloorApplied ? ` <span class="badge bg-warning text-dark ms-1" title="AI estimated less than the ${b.materialMinMinutes}-min minimum for this material — floored">min floor</span>` : ''}</td>
                    <td class="text-end">${fmt(b.laborCost)}</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Oven cure (${b.ovenCycleMinutes} min) <span class="text-muted fst-italic small">— shared, priced at quote level</span></td>
                    <td class="text-end text-muted">—</td>
                </tr>
                ${b.requiresPreheat ? `<tr>
                    <td class="px-3 text-muted">Outgassing preheat (${b.preheatMinutes} min)</td>
                    <td class="text-end">${fmt(b.preheatCost)}</td>
                </tr>` : ''}
                <tr class="border-top">
                    <td class="px-3 text-muted">Subtotal</td>
                    <td class="text-end fw-semibold">${fmt(b.subtotalBeforeComplexity)}</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Complexity – ${b.complexity}</td>
                    <td class="text-end">${fmt(b.complexityCharge)}</td>
                </tr>
                <tr>
                    <td class="px-3 text-muted">Markup</td>
                    <td class="text-end">${fmt(b.markupAmount)}</td>
                </tr>
                <tr class="border-top fw-bold">
                    <td class="px-2">Unit Price</td>
                    <td class="text-end text-success">${fmt(b.unitPrice)}</td>
                </tr>
            </tbody>
        </table>
        <p class="text-muted mb-0 mt-1" style="font-size:.75rem;">
            <i class="bi bi-info-circle me-1"></i>Labor rate and markup are set in <strong>Company Settings → Operating Costs</strong>.
        </p>
    </details>`;
}

function renderAiResultHtml(result) {
    const isSaved = !result.confidence;
    const confidenceClass = result.confidence === 'High' ? 'success' : result.confidence === 'Low' ? 'warning' : 'info';
    return `
        <div class="card border-${isSaved ? 'info' : 'success'}">
            <div class="card-header bg-${isSaved ? 'info' : 'success'} bg-opacity-10 d-flex align-items-center justify-content-between">
                <span class="fw-semibold"><i class="bi bi-${isSaved ? 'pencil-square text-info' : 'check-circle-fill text-success'} me-1"></i>${isSaved ? 'Saved Estimate — edit values below' : 'AI Estimate Ready'}</span>
                ${result.confidence ? `<span class="badge bg-${confidenceClass}">${escHtml(result.confidence)} Confidence</span>` : ''}
            </div>
            <div class="card-body pb-2">
                <div class="row g-3 mb-2">
                    <div class="col-12">
                        <label class="form-label fw-semibold mb-1">Description</label>
                        <input type="text" class="form-control" id="ai_descOverride"
                               value="${escHtml(result.description || '')}" placeholder="Item description…">
                    </div>
                    <div class="col-sm-3">
                        <label class="form-label fw-semibold mb-1">Sq Ft / Item</label>
                        <input type="number" class="form-control" id="ai_sqftOverride" step="0.01" min="0.01"
                               value="${result.surfaceAreaSqFt}" oninput="aiRecalcPrice()">
                    </div>
                    <div class="col-sm-3">
                        <label class="form-label fw-semibold mb-1">Complexity</label>
                        <select class="form-select" id="ai_complexityOverride" onchange="aiRecalcPrice()">
                            <option value="Simple"${result.complexity === 'Simple' ? ' selected' : ''}>Simple</option>
                            <option value="Moderate"${result.complexity === 'Moderate' ? ' selected' : ''}>Moderate</option>
                            <option value="Complex"${result.complexity === 'Complex' ? ' selected' : ''}>Complex</option>
                            <option value="Extreme"${result.complexity === 'Extreme' ? ' selected' : ''}>Extreme</option>
                        </select>
                    </div>
                    <div class="col-sm-3">
                        <label class="form-label fw-semibold mb-1">Est. Minutes</label>
                        <input type="number" class="form-control" id="ai_minutesOverride" min="1"
                               value="${result.estimatedMinutes}" oninput="aiRecalcPrice()">
                    </div>
                    <div class="col-sm-3">
                        <label class="form-label fw-semibold mb-1">Price Override</label>
                        <div class="input-group">
                            <span class="input-group-text">$</span>
                            <input type="number" class="form-control" id="ai_priceOverride" min="0" step="0.01"
                                   placeholder="${result.estimatedUnitPrice.toFixed(2)}">
                        </div>
                        <small class="text-muted">Leave blank to use engine calc</small>
                    </div>
                </div>
                <div class="alert alert-light py-2 mb-2">
                    <div class="d-flex justify-content-between">
                        <span><i class="bi bi-currency-dollar me-1"></i><strong>AI Estimated Unit Price:</strong></span>
                        <span id="ai_priceDisplay" class="fw-semibold text-success">$${result.estimatedUnitPrice.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2})}</span>
                    </div>
                    <div class="d-flex justify-content-between">
                        <span class="text-muted small"><i class="bi bi-stack me-1"></i>Estimated Total (× qty):</span>
                        <span class="text-muted small">$${result.estimatedTotal.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2})}</span>
                    </div>
                    <div class="mt-1" style="font-size:.78rem;color:#6c757d;">
                        <i class="bi bi-arrow-repeat me-1"></i>Price updates automatically when you change sq ft, minutes, or complexity above.
                    </div>
                </div>
                ${buildAiPriceBreakdown(result)}
                <div class="mb-2">
                    <div class="d-flex flex-wrap align-items-center gap-1" id="ai_tagList"></div>
                    <div class="input-group input-group-sm mt-1" style="max-width:280px">
                        <input type="text" class="form-control form-control-sm" id="ai_tagInput"
                               placeholder="Add tag, press Enter…" maxlength="40"
                               onkeydown="if(event.key==='Enter'||event.key===','){event.preventDefault();aiAddTag();}">
                        <button class="btn btn-outline-secondary btn-sm" type="button" onclick="aiAddTag()">
                            <i class="bi bi-plus"></i>
                        </button>
                    </div>
                </div>
                ${result.benchmark ? `
                <div class="alert alert-info py-2 mb-2">
                    <div class="d-flex align-items-center mb-1">
                        <i class="bi bi-clock-history me-2"></i>
                        <strong class="small">Historical Benchmark</strong>
                        <span class="badge bg-info ms-2">${result.benchmark.matchCount} similar job${result.benchmark.matchCount !== 1 ? 's' : ''}</span>
                    </div>
                    <div class="d-flex justify-content-between small">
                        <span class="text-muted">Range:</span>
                        <span>$${result.benchmark.minPrice.toFixed(2)} – $${result.benchmark.maxPrice.toFixed(2)}</span>
                    </div>
                    <div class="d-flex justify-content-between small fw-semibold">
                        <span class="text-muted">Avg price / item:</span>
                        <span>$${result.benchmark.avgPrice.toFixed(2)}</span>
                    </div>
                </div>` : ''}
                ${result.aiReasoning ? `
                <details class="mb-2">
                    <summary class="text-muted small" style="cursor:pointer"><i class="bi bi-info-circle me-1"></i>AI Reasoning</summary>
                    <p class="small text-muted mt-1 mb-0">${escHtml(result.aiReasoning)}</p>
                </details>` : ''}
                <details class="mb-2">
                    <summary class="text-muted small fw-semibold" style="cursor:pointer">
                        <i class="bi bi-question-circle me-1"></i>Price seems off? Here's what to do
                    </summary>
                    <div class="mt-2 p-2 bg-light rounded" style="font-size:.82rem;">
                        <p class="mb-2 fw-semibold">The price is built from: <em>labor + powder + oven + complexity + markup.</em>
                        The most common reason for a high estimate is the AI over-estimating minutes.</p>
                        <ol class="mb-2 ps-3">
                            <li class="mb-1"><strong>Check the breakdown</strong> (above) — expand "Price Breakdown" to see exactly which line is driving the cost.</li>
                            <li class="mb-1"><strong>Adjust Est. Minutes</strong> — the AI estimates time for the entire job including blasting, masking, cure, and cleanup. If it's too high, lower it and the price will update instantly.</li>
                            <li class="mb-1"><strong>Adjust Complexity</strong> — dropping from Moderate to Simple can meaningfully reduce the price if the item is straightforward.</li>
                            <li class="mb-1"><strong>Adjust Sq Ft</strong> — if you know the surface area is wrong, fix it here.</li>
                            <li class="mb-1"><strong>Override the price directly</strong> — enter your own number in the Price Override field. This always wins over the calculated price.</li>
                            <li class="mb-1"><strong>Labor rate or markup too high?</strong> Those are set company-wide in <strong>Settings → Operating Costs</strong> and affect all quotes.</li>
                        </ol>
                        <p class="mb-0 text-muted">The AI learns from accepted quotes over time — the more quotes you run without overriding, the better it calibrates to your shop's pricing.</p>
                    </div>
                </details>
                <button type="button" class="btn btn-sm btn-outline-secondary" onclick="aiReAnalyze()">
                    <i class="bi bi-arrow-repeat me-1"></i>Re-analyze
                </button>
            </div>
        </div>`;
}

// ─── AI Upload / Analysis Functions ──────────────────────────────────────────

function aiHandleFileSelect(input) {
    Array.from(input.files).forEach(aiUploadFile);
    input.value = ''; // reset so same file can be re-selected
}

function aiHandleDrop(event) {
    Array.from(event.dataTransfer.files).forEach(aiUploadFile);
}

// Resize + recompress an image file before upload so phone photos (5-15 MB)
// don't saturate mobile upload bandwidth or slow down Anthropic processing.
// Max 1200px on the long edge, JPEG at 85% quality — ~150-250 KB typical output.
// Non-image files and GIFs are returned unchanged.
async function aiCompressImage(file, maxPx = 1200, quality = 0.85) {
    if (!file.type.startsWith('image/') || file.type === 'image/gif') return file;
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.onload = e => {
            const img = new Image();
            img.onload = () => {
                const scale = Math.min(1, maxPx / Math.max(img.width, img.height));
                const w = Math.round(img.width * scale);
                const h = Math.round(img.height * scale);
                const canvas = document.createElement('canvas');
                canvas.width  = w;
                canvas.height = h;
                canvas.getContext('2d').drawImage(img, 0, 0, w, h);
                canvas.toBlob(blob => {
                    if (!blob || blob.size >= file.size) { resolve(file); return; }
                    resolve(new File([blob], file.name.replace(/\.[^.]+$/, '.jpg'), { type: 'image/jpeg' }));
                }, 'image/jpeg', quality);
            };
            img.onerror = () => resolve(file);
            img.src = e.target.result;
        };
        reader.onerror = () => resolve(file);
        reader.readAsDataURL(file);
    });
}

async function aiUploadFile(file) {
    // Compress before uploading — full-res phone photos slow upload + Anthropic API
    const compressed = await aiCompressImage(file);

    // Read compressed bytes for the thumbnail preview (blob: URLs blocked by CSP)
    const previewUrl = await new Promise(resolve => {
        const reader = new FileReader();
        reader.onload = e => resolve(e.target.result);
        reader.onerror = () => resolve('');
        reader.readAsDataURL(compressed);
    });
    const formData = new FormData();
    formData.append('file', compressed);
    formData.append('__RequestVerificationToken',
        document.querySelector('input[name="__RequestVerificationToken"]')?.value || '');

    const uploadUrl = (pageMeta.aiUploadUrl || '/Quotes/UploadAiPhoto');
    try {
        const resp = await fetch(uploadUrl, { method: 'POST', body: formData });
        const result = await resp.json();
        if (result.success) {
            wz.ai.tempIds.push(result.tempId);
            wz.ai.fileNames.push(result.fileName);
            wz.ai.previewUrls.push(previewUrl);
            aiRefreshPhotoList();
            document.getElementById('ai_photoError')?.classList.add('d-none');
        } else {
            aiShowError('Upload failed: ' + (result.error || 'Unknown error'));
        }
    } catch (err) {
        aiShowError('Upload error: ' + err.message);
    }
}

function aiRemovePhoto(index) {
    wz.ai.tempIds.splice(index, 1);
    wz.ai.fileNames.splice(index, 1);
    wz.ai.previewUrls.splice(index, 1);
    aiRefreshPhotoList();
}

function aiRefreshPhotoList() {
    const container = document.getElementById('ai_photoList');
    if (!container) return;
    container.innerHTML = wz.ai.tempIds.map((tid, i) => {
        const previewUrl = wz.ai.previewUrls[i] || '';
        const thumb = previewUrl
            ? `<img src="${escHtml(previewUrl)}" alt="" style="width:48px;height:48px;object-fit:cover;border-radius:4px;flex-shrink:0;">`
            : `<i class="bi bi-image text-primary fs-5"></i>`;
        return `<div class="d-flex align-items-center gap-2 p-2 border rounded bg-light mb-1">
            ${thumb}
            <span class="flex-grow-1 small text-truncate">${escHtml(wz.ai.fileNames[i] || tid)}</span>
            <button type="button" class="btn btn-sm btn-outline-danger" onclick="aiRemovePhoto(${i})"><i class="bi bi-x"></i></button>
        </div>`;
    }).join('');
}

async function aiAnalyze() {
    // Reset any prior session so each click is a clean analysis
    wz.ai.result = null;
    wz.ai.conversationHistory = [];
    wz.ai.accepted = false;
    wz.ai.tags = [];
    document.getElementById('ai_resultsSection')?.classList.add('d-none');
    document.getElementById('ai_followupSection')?.classList.add('d-none');

    // Validate
    if (wz.ai.tempIds.length === 0) {
        document.getElementById('ai_photoError')?.classList.remove('d-none');
        return;
    }
    const refDim = document.getElementById('ai_referenceDim')?.value?.trim();
    if (!refDim) {
        document.getElementById('ai_dimError')?.classList.remove('d-none');
        return;
    }
    document.getElementById('ai_dimError')?.classList.add('d-none');

    const qty          = parseFloat(document.getElementById('ai_quantity')?.value) || 1;
    const color        = document.getElementById('ai_color')?.value?.trim() || '';
    const coats        = parseInt(document.getElementById('ai_coatCount')?.value) || 1;
    const materialType = document.getElementById('ai_materialType')?.value?.trim() || '';
    const weightLbsRaw = parseFloat(document.getElementById('ai_weightLbs')?.value);
    const weightLbs    = isNaN(weightLbsRaw) || weightLbsRaw <= 0 ? null : weightLbsRaw;

    // Persist all input fields before any renderStep() re-renders the form
    wz.data.quantity       = qty;
    wz.data.aiReferenceDim = refDim;
    wz.data.aiColor        = color;
    wz.data.aiCoatCount    = coats;
    wz.data.aiMaterialType = materialType;
    wz.data.aiWeightLbs    = weightLbs;

    aiSetLoading(true);
    document.getElementById('ai_followupSection')?.classList.add('d-none');
    document.getElementById('ai_resultsSection')?.classList.add('d-none');
    document.getElementById('ai_errorAlert')?.classList.add('d-none');

    const blastSetupIdEl  = document.getElementById('ai_blastSetupId');
    const _defaultSetup   = blastSetupData.find(s => s.isDefault) || blastSetupData[0];
    const blastSetupId    = blastSetupIdEl
        ? (parseInt(blastSetupIdEl.value) || null)
        : (_defaultSetup ? _defaultSetup.id : null);

    const payload = {
        photoTempIds:        wz.ai.tempIds,
        referenceDimension:  refDim,
        materialType:        materialType || null,
        estimatedWeightLbs:  weightLbs,
        quantity:            qty,
        desiredColor:        color,
        coatCount:           coats,
        conversationHistory: wz.ai.conversationHistory,
        followUpAnswer:      null,
        blastSetupId:        blastSetupId
    };

    const analyzeUrl = pageMeta.aiAnalyzeUrl || '/Quotes/AiAnalyzeItem';
    const controller = new AbortController();
    // Abort after 120 s — server-side Anthropic timeout is 60 s per attempt with retries;
    // 120 s gives room for one retry plus network round-trip on a slow mobile connection.
    const hardTimeout   = setTimeout(() => controller.abort(), 120_000);
    // After 30 s without a response, update the spinner text so the user knows it's working.
    const slowWarning   = setTimeout(() => {
        const t = document.getElementById('ai_loadingText');
        if (t) t.textContent = 'Still analyzing… this can take a minute on mobile connections.';
    }, 30_000);
    try {
        const resp = await fetch(analyzeUrl, {
            method: 'POST',
            signal: controller.signal,
            headers: {
                'Content-Type': 'application/json',
                'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]')?.value || ''
            },
            body: JSON.stringify(payload)
        });
        clearTimeout(hardTimeout);
        clearTimeout(slowWarning);
        if (!resp.ok) {
            if (resp.status === 401 || resp.status === 302 || resp.redirected) {
                throw new Error('Your session has expired. Please refresh the page and sign in again.');
            }
            throw new Error(`Server error (${resp.status}). Please try again.`);
        }
        const contentType = resp.headers.get('Content-Type') || '';
        if (!contentType.includes('application/json')) {
            // Server returned HTML (e.g. login redirect) instead of JSON
            throw new Error('Your session may have expired. Please refresh the page and sign in again.');
        }
        const result = await resp.json();
        aiHandleResult(result);
    } catch (err) {
        clearTimeout(hardTimeout);
        clearTimeout(slowWarning);
        console.error('AI analyze error:', err);
        aiSetLoading(false);
        if (err.name === 'AbortError') {
            aiShowError('The request timed out — your connection may be slow. Please try again.');
        } else {
            aiShowError(err.message);
        }
    }
}

async function aiSendFollowup() {
    const answer = document.getElementById('ai_followupAnswer')?.value?.trim();
    if (!answer) return;

    aiSetLoading(true);
    document.getElementById('ai_followupSection')?.classList.add('d-none');

    const qty          = parseFloat(document.getElementById('ai_quantity')?.value) || 1;
    const color        = document.getElementById('ai_color')?.value?.trim() || '';
    const coats        = parseInt(document.getElementById('ai_coatCount')?.value) || 1;
    const ref          = document.getElementById('ai_referenceDim')?.value?.trim() || '';
    const materialType = document.getElementById('ai_materialType')?.value?.trim() || '';
    const weightLbsRaw = parseFloat(document.getElementById('ai_weightLbs')?.value);
    const weightLbs    = isNaN(weightLbsRaw) || weightLbsRaw <= 0 ? null : weightLbsRaw;
    wz.data.quantity = qty;  // persist before renderStep re-renders

    const blastSetupIdEl2  = document.getElementById('ai_blastSetupId');
    const _defaultSetup2   = blastSetupData.find(s => s.isDefault) || blastSetupData[0];
    const blastSetupId2    = blastSetupIdEl2
        ? (parseInt(blastSetupIdEl2.value) || null)
        : (_defaultSetup2 ? _defaultSetup2.id : null);

    const payload = {
        photoTempIds:        wz.ai.tempIds,
        referenceDimension:  ref,
        materialType:        materialType || null,
        estimatedWeightLbs:  weightLbs,
        quantity:            qty,
        desiredColor:        color,
        coatCount:           coats,
        conversationHistory: wz.ai.conversationHistory,
        followUpAnswer:      answer,
        blastSetupId:        blastSetupId2
    };

    const analyzeUrl = pageMeta.aiAnalyzeUrl || '/Quotes/AiAnalyzeItem';
    const controller2  = new AbortController();
    const hardTimeout2 = setTimeout(() => controller2.abort(), 120_000);
    const slowWarning2 = setTimeout(() => {
        const t = document.getElementById('ai_loadingText');
        if (t) t.textContent = 'Still analyzing… this can take a minute on mobile connections.';
    }, 30_000);
    try {
        const resp = await fetch(analyzeUrl, {
            method: 'POST',
            signal: controller2.signal,
            headers: {
                'Content-Type': 'application/json',
                'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]')?.value || ''
            },
            body: JSON.stringify(payload)
        });
        clearTimeout(hardTimeout2);
        clearTimeout(slowWarning2);
        if (!resp.ok) {
            if (resp.status === 401 || resp.status === 302 || resp.redirected) {
                throw new Error('Your session has expired. Please refresh the page and sign in again.');
            }
            throw new Error(`Server error (${resp.status}). Please try again.`);
        }
        const contentType = resp.headers.get('Content-Type') || '';
        if (!contentType.includes('application/json')) {
            throw new Error('Your session may have expired. Please refresh the page and sign in again.');
        }
        const result = await resp.json();
        aiHandleResult(result);
    } catch (err) {
        clearTimeout(hardTimeout2);
        clearTimeout(slowWarning2);
        console.error('AI follow-up error:', err);
        aiSetLoading(false);
        if (err.name === 'AbortError') {
            aiShowError('The request timed out — your connection may be slow. Please try again.');
        } else {
            aiShowError(err.message);
        }
    }
}

function aiHandleResult(result) {
    aiSetLoading(false);
    console.log('AI result:', result);

    if (!result.success) {
        aiShowError(result.errorMessage || 'AI analysis failed. Please try again.');
        return;
    }

    // Update conversation history for follow-up rounds
    wz.ai.conversationHistory = result.conversationHistory || [];

    if (result.needsFollowUp) {
        // Store follow-up state and re-render step so elements are guaranteed fresh
        wz.ai.phase = 'followup';
        wz.ai.followUpQuestion = result.followUpQuestion || 'Can you provide more details?';
        renderStep(wz.step);
        document.getElementById('ai_followupAnswer')?.focus();
        requestAnimationFrame(() => {
            document.getElementById('ai_followupSection')?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
        });
    } else {
        // Store result state and re-render step so elements are guaranteed fresh
        wz.ai.result = result;
        wz.ai.tags = [...(result.tags || [])];
        wz.ai.accepted = true;
        wz.ai.phase = 'result';
        renderStep(wz.step);
        document.getElementById('ai_acceptError')?.classList.add('d-none');
        requestAnimationFrame(() => {
            document.getElementById('ai_resultsSection')?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
        });
    }
}

function aiReAnalyze() {
    wz.ai.accepted = false;
    wz.ai.result = null;
    wz.ai.recalcUnitPrice = null;
    wz.ai.conversationHistory = [];
    wz.ai.tags = [];
    wz.ai.followUpQuestion = null;
    wz.ai.phase = 'upload';
    renderStep(wz.step);
}

function aiRenderTags() {
    const container = document.getElementById('ai_tagList');
    if (!container) return;
    container.innerHTML = wz.ai.tags.map(t =>
        `<span class="badge bg-secondary d-inline-flex align-items-center gap-1">
            ${escHtml(t)}
            <button type="button" class="btn-close btn-close-white" style="font-size:.5rem"
                    aria-label="Remove" onclick="aiRemoveTag(${JSON.stringify(t)})"></button>
         </span>`
    ).join('');
}

function aiAddTag() {
    const input = document.getElementById('ai_tagInput');
    if (!input) return;
    const val = input.value.trim().toLowerCase().replace(/,/g, '');
    if (!val) return;
    if (!wz.ai.tags.includes(val)) {
        wz.ai.tags.push(val);
        aiRenderTags();
    }
    input.value = '';
    input.focus();
}

function aiRemoveTag(tag) {
    wz.ai.tags = wz.ai.tags.filter(t => t !== tag);
    aiRenderTags();
}

function aiSetLoading(isLoading) {
    const btn     = document.getElementById('ai_analyzeBtn');
    const spinner = document.getElementById('ai_loadingSpinner');
    const text    = document.getElementById('ai_loadingText');
    if (btn) btn.disabled = isLoading;
    spinner?.classList.toggle('d-none', !isLoading);
    text?.classList.toggle('d-none', !isLoading);
    // Reset text so a retry after the slow-connection warning shows the default message
    if (!isLoading && text) text.textContent = 'Analyzing photos, please wait…';
}

function aiShowError(message) {
    console.error('AI error shown:', message);
    const el = document.getElementById('ai_errorAlert');
    if (el) {
        el.textContent = message;
        el.classList.remove('d-none');
        el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
}

// Step 3: Coating layers
function renderStep3Html() {
    const isSandblastOnly = !!wz.data.sandblastOnly;
    return `
        <div class="d-flex align-items-center border rounded py-2 px-3 mb-3 bg-light gap-2">
            <div class="form-check form-switch mb-0 flex-shrink-0">
                <input class="form-check-input" type="checkbox" id="sandblastOnlyToggle"
                       ${isSandblastOnly ? 'checked' : ''} onchange="onSandblastOnlyToggle()" style="cursor:pointer">
            </div>
            <label for="sandblastOnlyToggle" class="mb-0" style="line-height:1.3; cursor:pointer;">
                <strong>Sandblast / Prep Only</strong>
                <span class="d-block text-muted fw-normal small">No powder coating — no oven or powder costs</span>
            </label>
        </div>
        <div id="coatingSectionWrap"${isSandblastOnly ? ' class="d-none"' : ''}>
            <p class="text-muted small mb-3">
                <i class="bi bi-info-circle me-1"></i>
                Add one or more coating layers. The first coat uses 100% of the labor estimate;
                each additional coat adds 30%.
            </p>
            <div id="coatsListContainer"></div>
            <button type="button" class="btn btn-outline-success btn-sm mt-2" onclick="addCoatRow()">
                <i class="bi bi-plus-circle me-1"></i>Add Coating Layer
            </button>
        </div>
        ${isSandblastOnly ? `<div class="text-center text-muted py-3">
            <i class="bi bi-tools fs-3 d-block mb-2 opacity-50"></i>
            No powder coating — no oven or powder costs will be applied.
        </div>` : ''}`;
}

function onSandblastOnlyToggle() {
    const checked = document.getElementById('sandblastOnlyToggle')?.checked;
    wz.data.sandblastOnly = checked;
    if (checked) {
        wz.data.coats = [];
        // Keep manualUnitPrice — if the AI returned a price, preserve it as the sandblast price.
        // The user can adjust it; clearing it causes a $0 quote when prep services aren't configured.
    }
    renderStep(3);
}

function renderCoatsList() {
    // Sandblast-only items have no coating layers — don't auto-add a coat
    if (wz.data.sandblastOnly) return;
    const coats = wz.data.coats || [];
    if (coats.length === 0) {
        addCoatRow(); // auto-add a Base Coat
        return;
    }
    // Normalise powderType for coats loaded from server (they won't have this client-side field).
    // A coat with an inventoryItemId is stock; one with custom fields (colorName, colorCode,
    // supplierId, powderCostPerLb, powderToOrder) but no inventoryItemId is custom.
    coats.forEach(coat => {
        if (!coat.powderType) {
            const hasCustomFields = coat.colorName || coat.colorCode || coat.supplierId
                || coat.powderCostPerLb || coat.powderToOrder;
            coat.powderType = (coat.inventoryItemId || !hasCustomFields) ? 'stock' : 'custom';
        }
    });
    const container = document.getElementById('coatsListContainer');
    if (!container) return;
    container.innerHTML = '';
    coats.forEach((coat, i) => {
        container.insertAdjacentHTML('beforeend', buildCoatRowHtml(i, coat));
        restoreCoatRow(i, coat);
    });
    updateAllPowderNeeded();
}

function addCoatRow() {
    const coats = wz.data.coats = wz.data.coats || [];
    const i = coats.length;
    const defaultName = i === 0 ? 'Base Coat' : '';
    const coat = { coatName: defaultName, sequence: i + 1, inventoryItemId: null, powderType: wz.itemType === 'generic' ? 'custom' : 'stock' };
    coats.push(coat);

    const container = document.getElementById('coatsListContainer');
    if (!container) return;
    container.insertAdjacentHTML('beforeend', buildCoatRowHtml(i, coat));
    updatePowderNeeded(i);
    // AI items: recalculate price preview so multi-coat surcharge is visible before saving
    if (wz.itemType === 'ai') aiRecalcPrice();
}

const COAT_NAME_PRESETS = ['Primer', 'Base Coat', 'Mid Coat', 'Top Coat', 'Clear Coat'];

function buildCoatNameHtml(i, currentName) {
    const isPreset = !currentName || COAT_NAME_PRESETS.includes(currentName);
    const selectVal = isPreset ? (currentName || '') : '__other__';
    const textVal   = isPreset ? '' : (currentName || '');
    const options   = COAT_NAME_PRESETS.map(n =>
        `<option value="${n}"${selectVal === n ? ' selected' : ''}>${n}</option>`
    ).join('');
    return `
        <select class="form-select form-select-sm" style="min-width:150px;max-width:200px"
                id="coat_name_sel_${i}" onchange="onCoatNameSelect(${i})">
            <option value="">-- Select --</option>
            ${options}
            <option value="__other__"${selectVal === '__other__' ? ' selected' : ''}>Other...</option>
        </select>
        <input type="text" class="form-control form-control-sm" style="max-width:140px;display:${selectVal === '__other__' ? 'block' : 'none'}"
               id="coat_name_${i}" placeholder="Custom name" value="${escHtml(textVal)}">`;
}

function onCoatNameSelect(i) {
    const sel = document.getElementById(`coat_name_sel_${i}`);
    const txt = document.getElementById(`coat_name_${i}`);
    if (!sel || !txt) return;
    txt.style.display = sel.value === '__other__' ? 'block' : 'none';
    if (sel.value !== '__other__') txt.value = '';
}

function buildCoatRowHtml(i, coat) {
    const supplierOptions = supplierData.map(s =>
        `<option value="${s.value}">${escHtml(s.text)}</option>`
    ).join('');

    return `
        <div class="coat-row" id="coatRow_${i}" data-coat-index="${i}">
            <div class="d-flex align-items-center justify-content-between mb-2">
                <div class="d-flex align-items-center gap-2">
                    <span class="badge bg-secondary">#${i + 1}</span>
                    ${buildCoatNameHtml(i, coat.coatName)}
                </div>
                <button type="button" class="btn btn-sm btn-outline-danger" onclick="removeCoatRow(${i})">
                    <i class="bi bi-trash"></i>
                </button>
            </div>
            <!-- Powder type toggle -->
            <div class="d-flex gap-3 mb-2">
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="coat_type_${i}" id="coat_stock_${i}"
                           value="stock" ${(!coat.powderType || coat.powderType === 'stock') ? 'checked' : ''}
                           onchange="toggleCoatPowderType(${i})">
                    <label class="form-check-label" for="coat_stock_${i}">From inventory</label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="radio" name="coat_type_${i}" id="coat_custom_${i}"
                           value="custom" ${coat.powderType === 'custom' ? 'checked' : ''}
                           onchange="toggleCoatPowderType(${i})">
                    <label class="form-check-label" for="coat_custom_${i}">Custom / specify manually</label>
                </div>
            </div>
            <!-- Stock powder -->
            <div id="coat_stock_section_${i}" class="row g-2" style="display:${(!coat.powderType || coat.powderType === 'stock') ? 'flex' : 'none'}">
                <div class="col-12">
                    <div class="position-relative" id="coat_powder_wrapper_${i}">
                        <div class="input-group input-group-sm">
                            <input type="text" class="form-control form-control-sm" id="coat_powder_search_${i}"
                                   placeholder="Search by name, color, or code…" autocomplete="off"
                                   oninput="powderComboInput(${i})"
                                   onfocus="powderComboOpen(${i})"
                                   onkeydown="powderComboKey(event,${i})">
                            <button class="btn btn-outline-secondary btn-sm" type="button" tabindex="-1"
                                    onclick="powderComboToggle(${i})">
                                <i class="bi bi-chevron-down" style="font-size:.75rem;"></i>
                            </button>
                        </div>
                        <input type="hidden" id="coat_inventoryItemId_${i}">
                        <div id="coat_powder_dropdown_${i}"
                             class="powder-combo-dropdown"
                             style="display:none;max-height:220px;overflow-y:auto;z-index:1060;background:var(--bs-body-bg);color:var(--bs-body-color);border:1px solid var(--bs-border-color);border-radius:0.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);">
                        </div>
                    </div>
                </div>
                <div class="col-sm-4">
                    <label class="form-label form-label-sm">Coverage (sq ft/lb)</label>
                    <input type="number" class="form-control form-control-sm" id="coat_coverage_${i}" min="1" step="0.1" value="${coat.coverageSqFtPerLb || 30}"
                           oninput="updatePowderNeeded(${i})">
                </div>
                <div class="col-sm-4">
                    <label class="form-label form-label-sm">Transfer Efficiency (%)</label>
                    <input type="number" class="form-control form-control-sm" id="coat_efficiency_${i}" min="1" max="100" step="0.1" value="${coat.transferEfficiency || 65}"
                           oninput="updatePowderNeeded(${i})">
                </div>
                <div class="col-sm-4">
                    <label class="form-label form-label-sm">Cost ($/lb)</label>
                    <div class="input-group input-group-sm">
                        <span class="input-group-text">$</span>
                        <input type="number" class="form-control" id="coat_costPerLb_${i}" min="0" step="0.01" placeholder="auto" value="${coat.powderCostPerLb || ''}">
                    </div>
                </div>
                <!-- Shown only when an incoming (on-order) inventory powder is selected -->
                <div class="col-12" id="coat_incoming_section_${i}" style="display:${coat.isIncoming ? 'block' : 'none'}">
                    <div class="alert alert-warning py-2 mb-0">
                        <div class="fw-semibold"><i class="bi bi-truck me-1"></i>Incoming / On Order — powder not yet in stock</div>
                        <div class="small mt-1 mb-2">Pricing will charge for the full quantity ordered, not just calculated usage.</div>
                        <label class="form-label form-label-sm fw-semibold mb-1"><i class="bi bi-cart me-1"></i>Qty to Order (lbs)</label>
                        <div class="d-flex align-items-center gap-2">
                            <div class="input-group input-group-sm" style="max-width:200px">
                                <input type="number" class="form-control" id="coat_incoming_orderQty_${i}" min="0" step="0.01"
                                       placeholder="Lbs to order" value="${coat.isIncoming && coat.powderToOrder ? coat.powderToOrder : ''}">
                                <span class="input-group-text">lbs</span>
                            </div>
                            <span class="text-muted small">Calculated from area: <strong id="coat_incoming_calcQty_${i}">—</strong></span>
                        </div>
                    </div>
                </div>
            </div>
            <!-- Custom powder -->
            <div id="coat_custom_section_${i}" class="row g-2" style="display:${coat.powderType === 'custom' ? 'flex' : 'none'}">
                <!-- Catalog lookup row -->
                <div class="col-12">
                    <div class="d-flex align-items-center gap-2">
                        <div class="input-group input-group-sm flex-grow-1" style="max-width:360px;" id="coat_catalog_search_wrapper_${i}">
                            <span class="input-group-text bg-white"><i class="bi bi-search text-muted" style="font-size:.8rem;"></i></span>
                            <input type="text" class="form-control form-control-sm" id="coat_catalog_q_${i}"
                                   placeholder="Lookup from catalog (color name or SKU)…"
                                   oninput="customPowderCatalogInput(${i})"
                                   onkeydown="if(event.key==='Escape'){customPowderCatalogClose(${i})}"
                                   onblur="catalogQBlur(${i})"
                                   autocomplete="off">
                            <button type="button" class="btn btn-outline-secondary btn-sm" onclick="customPowderCatalogClose(${i})" title="Clear lookup">
                                <i class="bi bi-x" style="font-size:.8rem;"></i>
                            </button>
                        </div>
                        <span class="text-muted small fst-italic" style="font-size:.75rem;">or fill in manually below</span>
                    </div>
                    <div id="coat_catalog_results_${i}"
                         class="powder-combo-dropdown"
                         style="display:none;max-height:220px;overflow-y:auto;z-index:1060;background:var(--bs-body-bg);color:var(--bs-body-color);border:1px solid var(--bs-border-color);border-radius:0.375rem;box-shadow:0 4px 12px rgba(0,0,0,.12);">
                    </div>
                </div>
                <div class="col-sm-6">
                    <label class="form-label form-label-sm fw-semibold">Color Name <span class="text-danger">*</span> <small class="text-muted fw-normal">(shows on quote)</small></label>
                    <input type="text" class="form-control form-control-sm" id="coat_colorName_${i}" value="${escHtml(coat.colorName || '')}" placeholder="e.g., Gloss Black, Matte Red…" required>
                </div>
                <div class="col-sm-3">
                    <label class="form-label form-label-sm">Color Code</label>
                    <input type="text" class="form-control form-control-sm" id="coat_colorCode_${i}" value="${escHtml(coat.colorCode || '')}" placeholder="RAL-XXXX">
                </div>
                <div class="col-sm-3">
                    <label class="form-label form-label-sm">Finish</label>
                    <input type="text" class="form-control form-control-sm" id="coat_finish_${i}" value="${escHtml(coat.finish || '')}" placeholder="Gloss / Matte…">
                </div>
                <div class="col-sm-6">
                    <label class="form-label form-label-sm">Supplier (optional)</label>
                    <select class="form-select form-select-sm" id="coat_supplierId_${i}">
                        <option value="">-- None --</option>
                        ${supplierOptions}
                    </select>
                </div>
                <div class="col-sm-3">
                    <label class="form-label form-label-sm">Coverage (sq ft/lb)</label>
                    <input type="number" class="form-control form-control-sm" id="coat_custom_coverage_${i}" min="1" step="0.1" value="${coat.coverageSqFtPerLb || 30}"
                           oninput="updatePowderNeeded(${i})">
                </div>
                <div class="col-sm-3">
                    <label class="form-label form-label-sm">Transfer Eff. (%)</label>
                    <input type="number" class="form-control form-control-sm" id="coat_custom_efficiency_${i}" min="1" max="100" step="0.1" value="${coat.transferEfficiency || 65}"
                           oninput="updatePowderNeeded(${i})">
                </div>
                <div class="col-sm-4">
                    <label class="form-label form-label-sm">Cost ($/lb)</label>
                    <div class="input-group input-group-sm">
                        <span class="input-group-text">$</span>
                        <input type="number" class="form-control" id="coat_custom_costPerLb_${i}" min="0" step="0.01" placeholder="0.00" value="${coat.powderCostPerLb || ''}">
                    </div>
                </div>
                <!-- "Add to inventory as incoming" — shown after a catalog selection -->
                <div class="col-12" id="coat_custom_incoming_opt_${i}" style="display:${coat.catalogItemId ? 'block' : 'none'}">
                    <div class="form-check">
                        <input class="form-check-input" type="checkbox" id="coat_custom_addIncoming_${i}" ${coat.addAsIncoming ? 'checked' : ''}>
                        <label class="form-check-label small fw-semibold" for="coat_custom_addIncoming_${i}">
                            <i class="bi bi-truck text-warning me-1"></i>Add to inventory as Incoming Order (enables QR codes on work orders)
                        </label>
                    </div>
                </div>
                <input type="hidden" id="coat_custom_catalogItemId_${i}" value="${coat.catalogItemId || ''}">
                <div class="col-12">
                    <div class="alert alert-warning py-2 mb-0">
                        <label class="form-label form-label-sm fw-semibold mb-1"><i class="bi bi-cart me-1"></i>Qty to Order (lbs) — this powder must be purchased before the job</label>
                        <div class="d-flex align-items-center gap-2">
                            <div class="input-group input-group-sm" style="max-width:200px">
                                <input type="number" class="form-control" id="coat_custom_orderQty_${i}" min="0" step="0.01"
                                       placeholder="Enter lbs to order" value="${coat.powderToOrder != null ? coat.powderToOrder : ''}">
                                <span class="input-group-text">lbs</span>
                            </div>
                            <span class="text-muted small">Suggested from area: <strong id="coat_custom_calcQty_${i}">—</strong></span>
                        </div>
                    </div>
                </div>
            </div>
            <!-- Powder needed display (shared across stock/custom) -->
            <div class="alert alert-info py-2 mt-2 mb-0 d-flex align-items-center gap-2" id="coat_powderNeededRow_${i}" style="display:${(wz.data.surfaceAreaSqFt > 0) ? 'flex' : 'none'}!important">
                <i class="bi bi-box-seam"></i>
                <div>
                    <strong>Powder Needed for This Coat (Total Batch):</strong>
                    <span id="coat_powderNeededVal_${i}" class="fw-semibold ms-1">—</span>
                    <div class="small text-muted">This calculation is for the entire batch (all items × surface area)</div>
                </div>
            </div>
            <div class="mt-2 d-flex align-items-center gap-3">
                <input type="text" class="form-control form-control-sm" id="coat_notes_${i}"
                       value="${escHtml(coat.notes || '')}" placeholder="Notes about this coat layer (optional)">
                <div class="form-check form-check-inline text-nowrap mb-0 ms-2">
                    <input class="form-check-input" type="checkbox" id="coat_noExtraCharge_${i}"
                           ${coat.noExtraLayerCharge ? 'checked' : ''}>
                    <label class="form-check-label text-muted small" for="coat_noExtraCharge_${i}">
                        No extra layer charge
                    </label>
                </div>
            </div>
        </div>`;
}

function restoreCoatRow(i, coat) {
    // Restore inventory selection if present
    if (coat.inventoryItemId) {
        const hidden = document.getElementById(`coat_inventoryItemId_${i}`);
        if (hidden) hidden.value = coat.inventoryItemId;
        // Also fill the search display text
        const search = document.getElementById(`coat_powder_search_${i}`);
        if (search) {
            const powder = powderData.find(p => p.value == coat.inventoryItemId);
            if (powder) search.value = powder.text;
        }
    }
    // Restore supplier selection
    if (coat.supplierId) {
        const sel = document.getElementById(`coat_supplierId_${i}`);
        if (sel) sel.value = coat.supplierId;
    }
    // Restore order qty for custom powder
    if (coat.powderType === 'custom' && coat.powderToOrder != null) {
        const el = document.getElementById(`coat_custom_orderQty_${i}`);
        if (el) el.value = coat.powderToOrder;
    }
    // Restore incoming state for stock coats backed by an incoming inventory item
    if (coat.powderType !== 'custom' && coat.isIncoming) {
        const section = document.getElementById(`coat_incoming_section_${i}`);
        if (section) section.style.display = 'block';
        if (coat.powderToOrder != null) {
            const el = document.getElementById(`coat_incoming_orderQty_${i}`);
            if (el) el.value = coat.powderToOrder;
        }
    }
}

function removeCoatRow(i) {
    const coats = wz.data.coats || [];
    coats.splice(i, 1);
    // Re-render coat list
    const container = document.getElementById('coatsListContainer');
    if (!container) return;
    container.innerHTML = '';
    coats.forEach((coat, idx) => {
        container.insertAdjacentHTML('beforeend', buildCoatRowHtml(idx, coat));
        restoreCoatRow(idx, coat);
    });
    updateAllPowderNeeded();
    if (wz.itemType === 'ai') aiRecalcPrice();
}


function toggleCoatPowderType(i) {
    const type = document.querySelector(`input[name="coat_type_${i}"]:checked`)?.value || 'stock';
    document.getElementById(`coat_stock_section_${i}`).style.display  = type === 'stock'  ? 'flex' : 'none';
    document.getElementById(`coat_custom_section_${i}`).style.display = type === 'custom' ? 'flex' : 'none';
    updatePowderNeeded(i);
}

// ─── Powder combobox ──────────────────────────────────────────────────────────

function powderComboInput(i) {
    const q = document.getElementById(`coat_powder_search_${i}`)?.value?.toLowerCase() || '';
    powderComboRender(i, q);
    powderComboShow(i);
    // Clear the hidden value and incoming section when the user edits the text (forces a fresh pick)
    const hidden = document.getElementById(`coat_inventoryItemId_${i}`);
    if (hidden) hidden.value = '';
    const incomingSection = document.getElementById(`coat_incoming_section_${i}`);
    if (incomingSection) incomingSection.style.display = 'none';
}

function powderComboOpen(i) {
    const q = document.getElementById(`coat_powder_search_${i}`)?.value?.toLowerCase() || '';
    powderComboRender(i, q);
    powderComboShow(i);
}

function powderComboToggle(i) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    if (!dd) return;
    if (dd.style.display === 'none') {
        powderComboOpen(i);
        document.getElementById(`coat_powder_search_${i}`)?.focus();
    } else {
        powderComboClose(i);
    }
}

function powderComboRender(i, query) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    if (!dd) return;
    const filtered = query
        ? powderData.filter(p => p.text.toLowerCase().includes(query))
        : powderData;
    if (filtered.length === 0) {
        const qEnc = encodeURIComponent(query || '');
        dd.innerHTML = `<div class="px-3 py-2 text-muted small">No inventory match.</div>
            ${query && query.length >= 2 ? `<div class="px-2 pb-2">
                <button type="button" class="btn btn-sm btn-outline-warning w-100"
                        onmousedown="event.preventDefault(); powderCatalogSearch(${i}, '${query.replace(/'/g, "\\'")}')">
                    <i class="bi bi-search me-1"></i>Search Catalog &amp; Add as Incoming Order
                </button>
            </div>` : ''}`;
        return;
    }
    dd.innerHTML = filtered.map(p => {
        const badge = p.isIncoming
            ? '<span class="badge bg-warning text-dark ms-1" style="font-size:.7rem;vertical-align:middle;">Incoming</span>'
            : '';
        const displayText = p.isIncoming ? p.text.replace(/^\[INCOMING\]\s*/, '') : p.text;
        return `<div class="powder-opt" style="padding:.35rem .75rem;font-size:.83rem;white-space:normal;line-height:1.3;cursor:pointer;"
              data-val="${escHtml(String(p.value))}"
              data-txt="${escHtml(p.text)}"
              onmousedown="event.preventDefault(); powderComboSelect(${i}, this.dataset.val, this.dataset.txt)"
              onmouseenter="this.style.background='var(--bs-secondary-bg)'"
              onmouseleave="this.classList.contains('pw-active')?null:this.style.background=''">
            ${escHtml(displayText)}${badge}
         </div>`;
    }).join('');
}

function powderComboShow(i) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    const anchor = document.getElementById(`coat_powder_search_${i}`);
    if (!dd || !anchor) return;
    const rect = anchor.closest('.input-group').getBoundingClientRect();
    dd.style.position = 'fixed';
    dd.style.top = (rect.bottom + 2) + 'px';
    dd.style.left = rect.left + 'px';
    dd.style.width = (rect.width - 20) + 'px';
    dd.style.display = 'block';
}

function powderComboClose(i) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    if (dd) dd.style.display = 'none';
}

function powderComboSelect(i, value, text) {
    const hidden = document.getElementById(`coat_inventoryItemId_${i}`);
    const search = document.getElementById(`coat_powder_search_${i}`);
    if (hidden) hidden.value = value;
    if (search) search.value = text;
    powderComboClose(i);
    onPowderSelected(i);
}

function powderComboKey(event, i) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    if (!dd || dd.style.display === 'none') {
        if (event.key === 'ArrowDown' || event.key === 'Enter') {
            event.preventDefault();
            powderComboOpen(i);
        }
        return;
    }
    const items = Array.from(dd.querySelectorAll('.powder-opt'));
    let idx = items.findIndex(it => it.classList.contains('pw-active'));
    if (event.key === 'ArrowDown') {
        event.preventDefault();
        idx = Math.min(idx + 1, items.length - 1);
        items.forEach(it => { it.classList.remove('pw-active'); it.style.background = ''; });
        if (items[idx]) { items[idx].classList.add('pw-active'); items[idx].style.background = 'var(--bs-primary-bg-subtle)'; items[idx].scrollIntoView({ block: 'nearest' }); }
    } else if (event.key === 'ArrowUp') {
        event.preventDefault();
        idx = Math.max(idx - 1, 0);
        items.forEach(it => { it.classList.remove('pw-active'); it.style.background = ''; });
        if (items[idx]) { items[idx].classList.add('pw-active'); items[idx].style.background = 'var(--bs-primary-bg-subtle)'; items[idx].scrollIntoView({ block: 'nearest' }); }
    } else if (event.key === 'Enter') {
        event.preventDefault();
        const active = dd.querySelector('.pw-active') || items[0];
        if (active) active.dispatchEvent(new MouseEvent('mousedown'));
    } else if (event.key === 'Escape') {
        powderComboClose(i);
    }
}

// ─── Custom coat catalog lookup ───────────────────────────────────────────────

let customCatalogDebounce = null;

function customPowderCatalogInput(i) {
    clearTimeout(customCatalogDebounce);
    const q = document.getElementById(`coat_catalog_q_${i}`)?.value?.trim() || '';
    if (q.length < 2) {
        // Hide dropdown only — do NOT clear the input (that would erase the user's typing)
        const dd = document.getElementById(`coat_catalog_results_${i}`);
        if (dd) dd.style.display = 'none';
        return;
    }
    customCatalogDebounce = setTimeout(() => customPowderCatalogSearch(i, q), 300);
}

function customPowderCatalogSearch(i, query) {
    const dd = document.getElementById(`coat_catalog_results_${i}`);
    if (!dd) return;
    const anchor = document.getElementById(`coat_catalog_q_${i}`);
    dd.innerHTML = `<div class="px-3 py-2 text-muted small"><i class="bi bi-hourglass-split me-1"></i>Searching…</div>`;
    // Position relative to the search input wrapper
    const rect = anchor?.closest('.input-group')?.getBoundingClientRect();
    if (rect) {
        dd.style.position = 'fixed';
        dd.style.top  = (rect.bottom + 2) + 'px';
        dd.style.left = rect.left + 'px';
        dd.style.width = rect.width + 'px';
    }
    dd.style.display = 'block';

    fetch(`/Inventory/CatalogLookup?q=${encodeURIComponent(query)}`)
        .then(r => r.json())
        .then(results => {
            if (!results || results.length === 0) {
                dd.innerHTML = '<div class="px-3 py-2 text-muted small">No catalog matches. Enter details manually below.</div>';
                return;
            }
            dd.innerHTML = results.map(r => {
                const disc = r.isDiscontinued ? '<span class="badge bg-secondary ms-1" style="font-size:.7rem;">Discontinued</span>' : '';
                const price = r.unitPrice ? `<span class="text-muted small ms-1">$${parseFloat(r.unitPrice).toFixed(2)}/lb</span>` : '';
                return `<div class="powder-opt" style="padding:.4rem .75rem;font-size:.83rem;white-space:normal;line-height:1.3;cursor:pointer;"
                        onmousedown="event.preventDefault(); applyCustomCatalogResult(${i}, ${JSON.stringify(r).replace(/"/g, '&quot;')})"
                        onmouseenter="this.style.background='var(--bs-secondary-bg)'"
                        onmouseleave="this.style.background=''">
                    <strong>${escHtml(r.colorName)}</strong> — ${escHtml(r.vendorName)}
                    <span class="text-muted small ms-1">${escHtml(r.sku || '')}</span>
                    ${price}${disc}
                </div>`;
            }).join('');
        })
        .catch(() => {
            dd.innerHTML = '<div class="px-3 py-2 text-danger small">Search failed. Enter details manually.</div>';
        });
}

function customPowderCatalogClose(i) {
    const dd = document.getElementById(`coat_catalog_results_${i}`);
    if (dd) dd.style.display = 'none';
    const qEl = document.getElementById(`coat_catalog_q_${i}`);
    if (qEl) qEl.value = '';
}

// When the catalog search loses focus without a catalog result being selected,
// copy whatever the user typed into Color Name (if Color Name is still empty).
// This handles the common case of typing a color name in the search, not finding
// a catalog match, and expecting the typed text to become the color name.
function catalogQBlur(i) {
    const q     = document.getElementById(`coat_catalog_q_${i}`);
    const catId = document.getElementById(`coat_custom_catalogItemId_${i}`);
    const cn    = document.getElementById(`coat_colorName_${i}`);
    if (q && !catId?.value && q.value.trim() && cn && !cn.value.trim()) {
        cn.value = q.value.trim();
    }
}

function applyCustomCatalogResult(i, r) {
    customPowderCatalogClose(i);
    // Fill in the custom fields from the catalog result
    const set = (id, val) => { const el = document.getElementById(id); if (el && val != null) el.value = val; };
    set(`coat_colorName_${i}`, r.colorName);
    set(`coat_colorCode_${i}`, r.sku || '');
    set(`coat_finish_${i}`,    r.finish || '');
    if (r.coverageSqFtPerLb) set(`coat_custom_coverage_${i}`, r.coverageSqFtPerLb);
    if (r.transferEfficiency) set(`coat_custom_efficiency_${i}`, r.transferEfficiency);
    if (r.unitPrice)          set(`coat_custom_costPerLb_${i}`, parseFloat(r.unitPrice).toFixed(2));
    // Store catalog item ID and show "Add to inventory as Incoming" checkbox (default: checked)
    set(`coat_custom_catalogItemId_${i}`, r.id);
    const incomingOpt = document.getElementById(`coat_custom_incoming_opt_${i}`);
    if (incomingOpt) incomingOpt.style.display = 'block';
    const addIncomingCheck = document.getElementById(`coat_custom_addIncoming_${i}`);
    if (addIncomingCheck) addIncomingCheck.checked = true;
    // Try to match catalog vendor name to a local supplier
    const vendorLower = (r.vendorName || '').toLowerCase();
    if (vendorLower) {
        const supplierMatch = supplierData.find(s => {
            const sLower = s.text.toLowerCase();
            return sLower.includes(vendorLower) || vendorLower.includes(sLower);
        });
        if (supplierMatch) {
            const supplierSel = document.getElementById(`coat_supplierId_${i}`);
            if (supplierSel) supplierSel.value = supplierMatch.value;
        }
    }
    updatePowderNeeded(i);
}

// ─── Stock-side catalog search (fallback when no inventory match) ─────────────

/// <summary>
/// Searches the platform powder catalog for items matching the query string and renders
/// them in the dropdown as "Add as Incoming Order" options. If the user clicks one,
/// <see cref="createIncomingFromCatalog"/> POSTs to the server to create a 0-balance
/// inventory item with IsIncoming=true and then selects it for the current coat.
/// </summary>
function powderCatalogSearch(i, query) {
    const dd = document.getElementById(`coat_powder_dropdown_${i}`);
    if (!dd) return;
    dd.innerHTML = `<div class="px-3 py-2 text-muted small"><i class="bi bi-hourglass-split me-1"></i>Searching catalog…</div>`;
    powderComboShow(i);
    fetch(`/Inventory/CatalogLookup?q=${encodeURIComponent(query)}`)
        .then(r => r.json())
        .then(results => {
            if (!results || results.length === 0) {
                dd.innerHTML = '<div class="px-3 py-2 text-muted small">No catalog matches found. Try a different search term.</div>';
                return;
            }
            dd.innerHTML = `<div class="px-3 py-1 text-muted small fw-semibold border-bottom" style="font-size:.75rem;">Catalog Results — click to add as Incoming Order</div>` +
                results.map(r => {
                    const label = r.isDiscontinued
                        ? `<span class="badge bg-secondary ms-1" style="font-size:.7rem;">Discontinued</span>`
                        : '';
                    return `<div class="powder-opt" style="padding:.35rem .75rem;font-size:.83rem;white-space:normal;line-height:1.3;cursor:pointer;"
                            onmousedown="event.preventDefault(); createIncomingFromCatalog(${i}, ${r.id})"
                            onmouseenter="this.style.background='var(--bs-warning-bg-subtle)'"
                            onmouseleave="this.style.background=''">
                        <i class="bi bi-truck text-warning me-1"></i>
                        <strong>${escHtml(r.colorName)}</strong> — ${escHtml(r.vendorName)} ${escHtml(r.sku || '')}
                        <span class="text-muted small ms-1">$${parseFloat(r.unitPrice || 0).toFixed(2)}/lb</span>${label}
                    </div>`;
                }).join('');
        })
        .catch(() => {
            dd.innerHTML = '<div class="px-3 py-2 text-danger small">Catalog search failed. Please try again.</div>';
        });
}

function createIncomingFromCatalog(i, catalogItemId) {
    powderComboClose(i);
    const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
    const searchEl = document.getElementById(`coat_powder_search_${i}`);
    if (searchEl) searchEl.value = 'Adding to inventory…';

    const body = new URLSearchParams({ catalogItemId, __RequestVerificationToken: token || '' });
    fetch('/Inventory/CreateIncomingFromCatalog', { method: 'POST', body, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
        .then(r => r.json())
        .then(data => {
            if (!data.success) {
                if (searchEl) searchEl.value = '';
                alert(data.error || 'Failed to create inventory item.');
                return;
            }
            // Add the new item to powderData so it can be found by onPowderSelected
            powderData.push(data);
            // Select it as the current coat's powder
            powderComboSelect(i, data.value, data.text);
        })
        .catch(() => {
            if (searchEl) searchEl.value = '';
            alert('Failed to create inventory item. Please try again.');
        });
}

function onPowderSelected(i) {
    const sel = document.getElementById(`coat_inventoryItemId_${i}`);
    if (!sel || !sel.value) return;
    const powder = powderData.find(p => p.value === sel.value);
    if (!powder) return;
    const covEl  = document.getElementById(`coat_coverage_${i}`);
    const effEl  = document.getElementById(`coat_efficiency_${i}`);
    const costEl = document.getElementById(`coat_costPerLb_${i}`);
    if (covEl)  covEl.value  = powder.coverage;
    if (effEl)  effEl.value  = powder.efficiency;
    if (costEl && powder.costPerLb) costEl.value = parseFloat(powder.costPerLb).toFixed(2);

    // Show the incoming-order-qty section when the selected powder is incoming
    const incomingSection = document.getElementById(`coat_incoming_section_${i}`);
    if (incomingSection) incomingSection.style.display = powder.isIncoming ? 'block' : 'none';

    updatePowderNeeded(i);
}

function updatePowderNeeded(i) {
    const sqft = parseFloat(wz.data.surfaceAreaSqFt) || 0;
    const qty  = parseFloat(wz.data.quantity) || 1;
    if (sqft <= 0) return; // only meaningful for calculated items with surface area

    const isCustom = document.getElementById(`coat_custom_${i}`)?.checked;
    const covId = isCustom ? `coat_custom_coverage_${i}` : `coat_coverage_${i}`;
    const effId = isCustom ? `coat_custom_efficiency_${i}` : `coat_efficiency_${i}`;

    const cov = parseFloat(document.getElementById(covId)?.value) || 30;
    const eff = (parseFloat(document.getElementById(effId)?.value) || 65) / 100;

    const lbs = (sqft * qty) / (cov * eff);
    const valEl = document.getElementById(`coat_powderNeededVal_${i}`);
    if (valEl) valEl.textContent = lbs.toFixed(2) + ' lbs';

    // Update the suggested qty labels for custom and incoming order qty inputs
    const calcQtyEl = document.getElementById(`coat_custom_calcQty_${i}`);
    if (calcQtyEl) calcQtyEl.textContent = lbs.toFixed(2) + ' lbs';
    const incomingCalcEl = document.getElementById(`coat_incoming_calcQty_${i}`);
    if (incomingCalcEl) incomingCalcEl.textContent = lbs.toFixed(2) + ' lbs';
    // Pre-fill incoming order qty if empty
    const incomingQtyEl = document.getElementById(`coat_incoming_orderQty_${i}`);
    if (incomingQtyEl && !incomingQtyEl.value) incomingQtyEl.value = lbs.toFixed(2);
}

function updateAllPowderNeeded() {
    const count = wz.data.coats ? wz.data.coats.length : 0;
    for (let i = 0; i < count; i++) updatePowderNeeded(i);
}

// ─── Step 4: Prep Services ────────────────────────────────────────────────────

function renderStep4Html() {
    if (prepServiceData.length === 0) {
        return `<div class="text-center text-muted py-5">
            <i class="bi bi-tools fs-1 mb-2 d-block"></i>
            <p class="mb-1">No preparation services configured.</p>
            <p class="small">Add them in <strong>Company Settings → Prep Services</strong>.</p>
        </div>`;
    }

    const isSandblastOnly = !!wz.data.sandblastOnly;
    const isCatalog      = wz.itemType === 'product';
    const isAi           = wz.itemType === 'ai' && !isSandblastOnly;
    const includePrepCost = wz.data.includePrepCost ?? !isCatalog; // default ON for calculated, OFF for catalog
    const current        = wz.data.prepServices || [];

    const catalogBanner = isCatalog ? `
        <div class="alert alert-info py-2 mb-3">
            <i class="bi bi-info-circle me-1"></i>
            <strong>Catalog item:</strong> prep costs are already baked into the catalog price.
            Check the services needed for your records, or turn on the toggle below to add a separate prep charge.
            <div class="form-check form-switch mt-2 mb-0">
                <input class="form-check-input" type="checkbox" id="prep_includeCost"
                       ${includePrepCost ? 'checked' : ''} onchange="onPrepIncludeCostToggle()">
                <label class="form-check-label fw-semibold" for="prep_includeCost">Add prep charge to this item's price</label>
            </div>
        </div>` : '';

    const aiBanner = isAi ? `
        <div class="alert alert-success py-2 mb-3">
            <i class="bi bi-robot me-1"></i>
            <strong>AI estimate:</strong> prep costs are already included in the AI price.
            Select the services below for shop floor reference — they will <strong>not</strong> add to the item price.
        </div>` : '';

    const sandblastBanner = isSandblastOnly ? `
        <div class="alert alert-warning py-2 mb-3">
            <i class="bi bi-tools me-1"></i>
            <strong>Sandblast / Prep Only:</strong> estimated minutes will be billed as labor — no powder or oven costs.
        </div>` : '';

    const blastOptions = blastSetupData.length > 0
        ? blastSetupData.map(s => `<option value="${s.id}" ${s.isDefault ? 'selected' : ''}>${escHtml(s.name)}</option>`).join('')
        : '';

    const rows = prepServiceData.map(p => {
        const existing    = current.find(c => c.prepServiceId === p.id);
        const checked     = existing ? 'checked' : '';
        const minutes     = existing ? existing.estimatedMinutes : '';
        const blastSelId  = existing?.blastSetupId || null;

        const blastSelector = (p.requiresBlastSetup && blastSetupData.length > 0) ? `
            <div id="prep_blast_group_${p.id}" class="${checked ? '' : 'd-none'} mt-1">
                <label class="form-label form-label-sm text-muted mb-1"><i class="bi bi-wind me-1"></i>Blast Setup</label>
                <select class="form-select form-select-sm" id="prep_blast_${p.id}">
                    ${blastSetupData.map(s => `<option value="${s.id}" ${(blastSelId === s.id || (!blastSelId && s.isDefault)) ? 'selected' : ''}>${escHtml(s.name)}</option>`).join('')}
                </select>
            </div>` : '';

        return `
        <div class="py-2 border-bottom">
            <div class="d-flex align-items-center gap-3">
                <div class="form-check mb-0 flex-grow-1">
                    <input class="form-check-input" type="checkbox" id="prep_${p.id}"
                           value="${p.id}" ${checked} onchange="onPrepToggle(${p.id})">
                    <label class="form-check-label fw-semibold" for="prep_${p.id}">${escHtml(p.name)}</label>
                    ${p.description ? `<div class="text-muted small">${escHtml(p.description)}</div>` : ''}
                </div>
                <div id="prep_min_group_${p.id}" class="${checked ? '' : 'd-none'}" style="width:140px;">
                    <div class="input-group input-group-sm">
                        <input type="number" class="form-control" id="prep_min_${p.id}"
                               min="1" value="${minutes}" placeholder="Minutes">
                        <span class="input-group-text">min</span>
                    </div>
                </div>
            </div>
            ${blastSelector}
        </div>`;
    }).join('');

    const hint = isCatalog
        ? ''
        : `<div class="text-muted small mb-3">Check each prep step needed and enter an estimated time. Labor cost is added to this item's total.</div>`;

    return `${catalogBanner}${aiBanner}${sandblastBanner}${hint}${rows}`;
}

function onPrepIncludeCostToggle() {
    wz.data.includePrepCost = document.getElementById('prep_includeCost')?.checked ?? false;
}

function onPrepToggle(id) {
    const checked    = document.getElementById(`prep_${id}`)?.checked;
    const group      = document.getElementById(`prep_min_group_${id}`);
    const blastGroup = document.getElementById(`prep_blast_group_${id}`);
    if (group)      group.classList.toggle('d-none', !checked);
    if (blastGroup) blastGroup.classList.toggle('d-none', !checked);
    if (checked)    document.getElementById(`prep_min_${id}`)?.focus();
}

function collectStep4() {
    // Catalog items: read the toggle (default false); calculated items: always true
    if (wz.itemType === 'product') {
        wz.data.includePrepCost = document.getElementById('prep_includeCost')?.checked ?? false;
    } else {
        wz.data.includePrepCost = true;
    }

    wz.data.prepServices = prepServiceData
        .filter(p => document.getElementById(`prep_${p.id}`)?.checked)
        .map(p => {
            const blastEl = document.getElementById(`prep_blast_${p.id}`);
            return {
                prepServiceId:    p.id,
                prepServiceName:  p.name,
                estimatedMinutes: parseInt(document.getElementById(`prep_min_${p.id}`)?.value) || 0,
                blastSetupId:     blastEl ? (parseInt(blastEl.value) || null) : null
            };
        });
}

// ─── Data collection / validation ─────────────────────────────────────────────

function collectCurrentStep() {
    if (wz.step === 1) {
        if (!wz.itemType) {
            document.getElementById('typeError')?.classList.remove('d-none');
            return false;
        }
        return true;
    }

    if (wz.step === 2) {
        return collectStep2();
    }

    if (wz.step === 3) {
        collectStep3();
        return true;
    }

    if (wz.step === 4) {
        collectStep4();
        return true;
    }

    if (wz.step === 5) return true; // Step 5 actions handled by saveToCatalogFromWizard / skipCatalogSave

    return true;
}

function collectStep2() {
    let valid = true;

    if (wz.itemType === 'product') {
        const sel = document.getElementById('wz_catalogItemId');
        const val = sel?.value;
        if (!val) {
            document.getElementById('err_catalogItemId')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_catalogItemId')?.classList.add('d-none');
            wz.data.catalogItemId = parseInt(val);
            // Auto-fill description, surface area, and estimated minutes from catalog
            const cat = catalogData.find(c => c.value === val);
            wz.data.description = cat ? cat.text.split(' > ').pop().split(' - ')[0] : 'Product Item';
            wz.data.surfaceAreaSqFt = cat ? (parseFloat(cat.approxArea) || 0) : 0;
            wz.data.estimatedMinutes = cat ? (parseInt(cat.defaultMinutes) || 0) : 0;
        }
        wz.data.quantity = parseFloat(document.getElementById('wz_quantity')?.value) || 1;
        const override = parseFloat(document.getElementById('wz_powderCostOverride')?.value);
        wz.data.powderCostOverride = isNaN(override) ? null : override;
    }

    if (wz.itemType === 'calculated') {
        const desc = document.getElementById('wz_description')?.value?.trim();
        if (!desc) {
            document.getElementById('err_description')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_description')?.classList.add('d-none');
            wz.data.description = desc;
        }

        wz.data.complexity = document.getElementById('wz_complexity')?.value || 'Simple';

        const sqft = parseFloat(document.getElementById('wz_surfaceAreaSqFt')?.value);
        if (!sqft || sqft <= 0) {
            document.getElementById('err_surfaceAreaSqFt')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_surfaceAreaSqFt')?.classList.add('d-none');
            wz.data.surfaceAreaSqFt = sqft;
        }

        wz.data.quantity = parseFloat(document.getElementById('wz_quantity')?.value) || 1;
        wz.data.estimatedMinutes = parseInt(document.getElementById('wz_estimatedMinutes')?.value) || 30;
    }

    if (wz.itemType === 'generic') {
        const desc = document.getElementById('wz_description')?.value?.trim();
        if (!desc) {
            document.getElementById('err_description')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_description')?.classList.add('d-none');
            wz.data.description = desc;
        }

        const price = parseFloat(document.getElementById('wz_manualUnitPrice')?.value);
        if (isNaN(price) || price < 0) {
            document.getElementById('err_manualUnitPrice')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_manualUnitPrice')?.classList.add('d-none');
            wz.data.manualUnitPrice = price;
        }

        wz.data.quantity = parseFloat(document.getElementById('wz_quantity')?.value) || 1;
        wz.data.notes = document.getElementById('wz_notes')?.value?.trim() || null;
        wz.data.surfaceAreaSqFt = 0;
        wz.data.estimatedMinutes = 0;
        wz.data.isGenericItem = true;
    }

    if (wz.itemType === 'labor') {
        const desc = document.getElementById('wz_description')?.value?.trim();
        if (!desc) {
            document.getElementById('err_description')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_description')?.classList.add('d-none');
            wz.data.description = desc;
        }

        wz.data.quantity = parseFloat(document.getElementById('wz_quantity')?.value) || 1;
        wz.data.notes = document.getElementById('wz_notes')?.value?.trim() || null;
        wz.data.surfaceAreaSqFt = 0;
        wz.data.estimatedMinutes = 0;
        wz.data.isLaborItem = true;
    }

    if (wz.itemType === 'sales') {
        const itemId = wz.data.salesCatalogItemId;
        if (!itemId) {
            document.getElementById('err_salesCatalogItemId')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_salesCatalogItemId')?.classList.add('d-none');
            // Pull description and sku from the merchandise data
            const merch = merchandiseData.find(m => m.id === itemId);
            wz.data.description = merch ? merch.name : '';
            wz.data.sku         = merch ? (merch.sku || null) : null;
        }

        const price = parseFloat(document.getElementById('wz_manualUnitPrice')?.value);
        if (isNaN(price) || price < 0) {
            document.getElementById('err_manualUnitPrice')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_manualUnitPrice')?.classList.add('d-none');
            wz.data.manualUnitPrice = price;
        }

        wz.data.quantity         = parseFloat(document.getElementById('wz_quantity')?.value) || 1;
        wz.data.notes            = document.getElementById('wz_notes')?.value?.trim() || null;
        wz.data.surfaceAreaSqFt  = 0;
        wz.data.estimatedMinutes = 0;
        wz.data.isSalesItem      = true;
    }

    if (wz.itemType === 'formula') {
        const templateId = parseInt(document.getElementById('wz_formulaTemplate')?.value);
        if (!templateId) {
            document.getElementById('err_formulaTemplate')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_formulaTemplate')?.classList.add('d-none');
            wz.data.customItemTemplateId = templateId;
        }

        if (wz.data.formulaResult == null) {
            document.getElementById('err_formulaCalc')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('err_formulaCalc')?.classList.add('d-none');
        }

        if (valid) {
            const templates = pageMeta.customFormulaTemplates || [];
            const tmpl = templates.find(t => t.id === templateId);
            const qty = parseFloat(document.getElementById('wz_formula_qty')?.value) || 1;
            const descEl = document.getElementById('wz_formula_description');

            wz.data.quantity = qty;
            wz.data.description = descEl?.value?.trim() || (tmpl?.name ?? 'Custom Formula Item');
            wz.data.isCustomFormulaItem = true;
            wz.data.formulaFieldValuesJson = JSON.stringify(wz.data.formulaVariables || {});

            if (tmpl?.outputMode === 'FixedRate') {
                wz.data.manualUnitPrice = parseFloat(wz.data.formulaResult) || 0;
                wz.data.surfaceAreaSqFt = 0;
                wz.data.estimatedMinutes = 0;
            } else {
                // SurfaceAreaSqFt mode — formula produced sq ft; standard engine prices it
                wz.data.surfaceAreaSqFt = parseFloat(wz.data.formulaResult) || 0;
                wz.data.manualUnitPrice = null;
                wz.data.estimatedMinutes = 0;
            }
        }
    }

    if (wz.itemType === 'ai') {
        if (!wz.ai.accepted || !wz.ai.result) {
            document.getElementById('ai_acceptError')?.classList.remove('d-none');
            valid = false;
        } else {
            document.getElementById('ai_acceptError')?.classList.add('d-none');
            // Collect user overrides (or fall back to AI result)
            const desc = document.getElementById('ai_descOverride')?.value?.trim() || wz.ai.result.description;
            const sqft = parseFloat(document.getElementById('ai_sqftOverride')?.value) || wz.ai.result.surfaceAreaSqFt;
            const complexity = document.getElementById('ai_complexityOverride')?.value || wz.ai.result.complexity;
            const minutes = parseInt(document.getElementById('ai_minutesOverride')?.value) || wz.ai.result.estimatedMinutes;
            const priceOverrideVal = parseFloat(document.getElementById('ai_priceOverride')?.value);

            wz.data.description       = desc;
            wz.data.surfaceAreaSqFt   = sqft;
            wz.data.complexity        = complexity;
            wz.data.estimatedMinutes  = minutes;
            wz.data.quantity          = parseFloat(document.getElementById('ai_quantity')?.value) || 1;
            wz.data.aiReferenceDim    = document.getElementById('ai_referenceDim')?.value?.trim() || '';
            wz.data.aiColor           = document.getElementById('ai_color')?.value?.trim() || '';
            wz.data.aiCoatCount       = parseInt(document.getElementById('ai_coatCount')?.value) || 1;
            wz.data.aiMaterialType    = document.getElementById('ai_materialType')?.value?.trim() || '';
            const _wlbs = parseFloat(document.getElementById('ai_weightLbs')?.value);
            wz.data.aiWeightLbs       = isNaN(_wlbs) || _wlbs <= 0 ? null : _wlbs;
            wz.data.powderCostOverride = !isNaN(priceOverrideVal) ? priceOverrideVal : null;
            // Store the final price: manual override → recalculated price → original AI estimate
            wz.data.manualUnitPrice = !isNaN(priceOverrideVal)
                ? priceOverrideVal
                : (wz.ai.recalcUnitPrice ?? wz.ai.result?.estimatedUnitPrice ?? 0);
            wz.data.aiPhotoTempIds    = [...wz.ai.tempIds];
            wz.data.aiPhotoFileNames  = [...wz.ai.fileNames];
            wz.data.aiPhotoPreviewUrls = [...wz.ai.previewUrls];
            wz.data.isAiItem          = true;
            wz.data.aiTags            = [...wz.ai.tags];
            wz.data.aiPredictionId    = wz.ai.result?.aiPredictionId ?? null;
        }
    }

    return valid;
}

function collectStep3() {
    const rows = document.querySelectorAll('.coat-row');
    const coats = [];
    rows.forEach((row, i) => {
        const isCustom = document.querySelector(`input[name="coat_type_${i}"]:checked`)?.value === 'custom';
        const coat = {
            coatName:   (() => { const s = document.getElementById(`coat_name_sel_${i}`)?.value; return (s && s !== '__other__') ? s : (document.getElementById(`coat_name_${i}`)?.value?.trim() || `Coat ${i + 1}`); })(),
            sequence:   i + 1,
            powderType: isCustom ? 'custom' : 'stock',
            notes:              document.getElementById(`coat_notes_${i}`)?.value?.trim() || null,
            noExtraLayerCharge: document.getElementById(`coat_noExtraCharge_${i}`)?.checked || false,
        };

        if (!isCustom) {
            const invId = document.getElementById(`coat_inventoryItemId_${i}`)?.value;
            coat.inventoryItemId  = invId ? parseInt(invId) : null;
            // Resolve color name and incoming flag from powderData for display purposes
            let isIncomingCoat = false;
            if (coat.inventoryItemId) {
                const powder = powderData.find(p => p.value === String(invId));
                if (powder) {
                    coat.colorName = powder.colorName || null;
                    isIncomingCoat = powder.isIncoming || false;
                }
            }
            coat.coverageSqFtPerLb  = parseFloat(document.getElementById(`coat_coverage_${i}`)?.value)   || 30;
            coat.transferEfficiency = parseFloat(document.getElementById(`coat_efficiency_${i}`)?.value)  || 65;
            const costEl = document.getElementById(`coat_costPerLb_${i}`)?.value;
            coat.powderCostPerLb = costEl ? parseFloat(costEl) : null;
            coat.isIncoming = isIncomingCoat;
        } else {
            coat.colorName       = document.getElementById(`coat_colorName_${i}`)?.value?.trim() || null;
            coat.colorCode       = document.getElementById(`coat_colorCode_${i}`)?.value?.trim() || null;
            coat.finish          = document.getElementById(`coat_finish_${i}`)?.value?.trim()    || null;
            const suppId = document.getElementById(`coat_supplierId_${i}`)?.value;
            coat.supplierId      = suppId ? parseInt(suppId) : null;
            coat.coverageSqFtPerLb  = parseFloat(document.getElementById(`coat_custom_coverage_${i}`)?.value)   || 30;
            coat.transferEfficiency = parseFloat(document.getElementById(`coat_custom_efficiency_${i}`)?.value)  || 65;
            const costEl = document.getElementById(`coat_custom_costPerLb_${i}`)?.value;
            coat.powderCostPerLb = costEl ? parseFloat(costEl) : null;
            // Catalog lookup result fields
            const catId = document.getElementById(`coat_custom_catalogItemId_${i}`)?.value;
            coat.catalogItemId   = catId ? parseInt(catId) : null;
            coat.addAsIncoming   = document.getElementById(`coat_custom_addIncoming_${i}`)?.checked || false;
        }

        // Powder to order: custom/incoming coats read from the user-entered field; in-stock auto-calculates
        if (isCustom) {
            const orderQtyVal = document.getElementById(`coat_custom_orderQty_${i}`)?.value;
            coat.powderToOrder = orderQtyVal ? parseFloat(orderQtyVal) : null;
        } else if (coat.isIncoming) {
            const orderQtyVal = document.getElementById(`coat_incoming_orderQty_${i}`)?.value;
            coat.powderToOrder = orderQtyVal ? parseFloat(orderQtyVal) : null;
        } else {
            const sqft = parseFloat(wz.data.surfaceAreaSqFt) || 0;
            const qty  = parseFloat(wz.data.quantity) || 1;
            if (sqft > 0 && coat.coverageSqFtPerLb > 0) {
                const eff = (coat.transferEfficiency || 65) / 100;
                coat.powderToOrder = (sqft * qty) / (coat.coverageSqFtPerLb * eff);
            }
        }

        coats.push(coat);
    });

    wz.data.coats = coats;
}

function preFillStep2() {
    const d = wz.data;
    const set = (id, val) => {
        const el = document.getElementById(id);
        if (el && val !== undefined && val !== null) el.value = val;
    };

    set('wz_quantity', d.quantity);
    set('wz_description', d.description);
    set('wz_surfaceAreaSqFt', d.surfaceAreaSqFt || '');
    set('wz_estimatedMinutes', d.estimatedMinutes || '');
    set('wz_manualUnitPrice', d.manualUnitPrice ?? '');
    set('wz_powderCostOverride', d.powderCostOverride ?? '');
    set('wz_notes', d.notes || '');

    if (wz.itemType === 'product' && d.catalogItemId) {
        const listItem = document.querySelector(`#catalogListbox [data-value="${d.catalogItemId}"]`);
        if (listItem) {
            pickCatalogItem(listItem);
            listItem.scrollIntoView({ block: 'nearest' });
        }
    }

    if (wz.itemType === 'calculated') {
        const cplx = document.getElementById('wz_complexity');
        if (cplx && d.complexity) cplx.value = d.complexity;
    }
}

// ─── Build item from wizard data ──────────────────────────────────────────────

function buildItemFromWizard() {
    const d = wz.data;
    const isSandblastOnly = !!d.sandblastOnly;
    // AI flag is preserved even for sandblast-only so the server uses the AI price (manualUnitPrice).
    // Without this, sandblast-only AI items fall through to the pricing engine and return $0 when
    // no prep services with minutes are configured.
    const isAi = wz.itemType === 'ai';
    return {
        description:       d.description || null,
        quantity:          d.quantity || 1,
        surfaceAreaSqFt:   d.surfaceAreaSqFt || 0,
        estimatedMinutes:  d.estimatedMinutes || 0,
        catalogItemId:     d.catalogItemId || null,
        manualUnitPrice:   isAi ? (d.manualUnitPrice ?? null) : (d.isGenericItem || d.isSalesItem || d.isCustomFormulaItem ? (d.manualUnitPrice ?? null) : null),
        powderCostOverride: d.powderCostOverride ?? null,
        isGenericItem:     !!d.isGenericItem,
        isLaborItem:       !!d.isLaborItem,
        isSalesItem:       !!d.isSalesItem,
        salesCatalogItemId: d.salesCatalogItemId || null,
        sku:               d.sku || null,
        isAiItem:          isAi,
        isCustomFormulaItem: !!d.isCustomFormulaItem,
        customItemTemplateId: d.customItemTemplateId || null,
        formulaFieldValuesJson: d.formulaFieldValuesJson || null,
        requiresSandblasting: false,
        requiresMasking:   false,
        notes:             d.notes || null,
        coats:             isSandblastOnly ? [] : (d.coats || []),
        prepServices:      d.prepServices || [],
        includePrepCost:   d.includePrepCost ?? (wz.itemType !== 'product'),
        complexity:        d.complexity || 'Simple',
        // Keep AI photos even for sandblast-only so they get promoted to permanent storage
        aiPhotoTempIds:    wz.itemType === 'ai' ? (d.aiPhotoTempIds || []) : [],
        aiPhotoFileNames:  wz.itemType === 'ai' ? (d.aiPhotoFileNames || []) : [],
        aiTags:            isAi ? ((d.aiTags || []).join ? (d.aiTags || []).join(',') : d.aiTags) || null : null,
        aiPredictionId:    isAi ? (d.aiPredictionId ?? null) : null
    };
}

// ─── Summary cards ────────────────────────────────────────────────────────────

function renderAllCards() {
    const container = document.getElementById('itemCardsContainer');
    const emptyMsg  = document.getElementById('itemsEmptyMessage');
    if (!container) return;

    if (quoteItems.length === 0) {
        container.innerHTML = '';
        if (emptyMsg) emptyMsg.style.display = 'block';
        return;
    }
    if (emptyMsg) emptyMsg.style.display = 'none';

    container.innerHTML = `
        <div class="d-flex gap-2 px-1 mb-1" style="font-size:.7rem; font-weight:600; text-transform:uppercase; letter-spacing:.05em; color:#6c757d;">
            <div class="flex-grow-1">Description</div>
            <div class="text-center flex-shrink-0" style="min-width:45px;">Qty</div>
            <div class="text-end flex-shrink-0" style="min-width:80px;">Unit Price</div>
            <div class="text-end flex-shrink-0" style="min-width:80px;">Total</div>
            <div style="min-width:66px;"></div>
        </div>` + quoteItems.map((item, i) => buildCardHtml(item, i)).join('');
}

function buildCardHtml(item, i) {
    const typeInfo = item.isLaborItem          ? { label: 'Labor',       cls: 'info',    icon: 'bi-person-gear' }
                   : item.isGenericItem        ? { label: 'Flat-Rate',   cls: 'warning', icon: 'bi-tag' }
                   : item.isSalesItem          ? { label: 'Merchandise', cls: 'success', icon: 'bi-shop' }
                   : item.catalogItemId        ? { label: 'Product',     cls: 'primary', icon: 'bi-bag-check' }
                   : item.isAiItem             ? { label: 'AI Quoted',   cls: 'secondary', icon: 'bi-robot' }
                   : item.isCustomFormulaItem  ? { label: 'Formula',     cls: 'purple',  icon: 'bi-calculator' }
                   :                            { label: 'Custom',       cls: 'success', icon: 'bi-rulers' };

    const coatCount = item.coats?.length || 0;
    const isPrepOnly = coatCount === 0 && !item.isGenericItem && !item.isLaborItem && !item.isSalesItem && !item.catalogItemId;
    const coatBadge = coatCount > 0
        ? `<span class="badge bg-secondary ms-1">${coatCount} coat${coatCount > 1 ? 's' : ''}</span>`
        : isPrepOnly
        ? `<span class="badge bg-warning text-dark ms-1" title="No powder coating"><i class="bi bi-tools me-1"></i>Prep Only</span>`
        : '';

    const complexityBadge = (!item.isGenericItem && !item.isLaborItem && !item.catalogItemId && item.complexity && item.complexity !== 'Simple')
        ? `<span class="badge bg-secondary bg-opacity-10 text-secondary border ms-1" style="font-size:.65em;">${escHtml(item.complexity)}</span>`
        : '';

    const priceLine = item.isGenericItem
        ? `$${fmtNum(item.manualUnitPrice)} × ${item.quantity}`
        : item.isLaborItem
        ? `${item.quantity} hr${item.quantity !== 1 ? 's' : ''}`
        : item.isSalesItem
        ? `$${fmtNum(item.manualUnitPrice)} × ${item.quantity}${item.sku ? ` · <span class="text-muted small">${escHtml(item.sku)}</span>` : ''}`
        : item.isCustomFormulaItem && item.manualUnitPrice != null
        ? `Total: $${fmtNum(item.manualUnitPrice)}${item.quantity > 1 ? ` · Qty: ${item.quantity}` : ''}`
        : item.surfaceAreaSqFt
        ? `${item.quantity} × ${fmtNum(item.surfaceAreaSqFt)} ${pageMeta.areaUnit || 'sq ft'}`
        : `Qty: ${item.quantity}`;

    const coatsHtml = (item.coats || []).map(c => {
        const color = c.colorName ? escHtml(c.colorName) : '';
        const code  = c.colorCode ? ` <span class="opacity-75">(${escHtml(c.colorCode)})</span>` : '';
        const orderBadge = (!c.inventoryItemId && c.powderToOrder)
            ? ` <span class="badge bg-warning text-dark" style="font-size:.65em;vertical-align:middle;" title="Custom powder — must be purchased before coating"><i class="bi bi-cart me-1"></i>ORDER ${parseFloat(c.powderToOrder).toFixed(2)} lbs</span>`
            : '';
        const noColorHint = (!color && !c.inventoryItemId && c.powderToOrder)
            ? ` <span class="badge bg-danger" style="font-size:.65em;vertical-align:middle;" title="Edit this item and enter a Color Name for the coat">No color specified</span>`
            : '';
        const coatNotes = c.notes ? `<span class="fst-italic ms-1 opacity-75">— ${escHtml(c.notes)}</span>` : '';
        return `<div style="font-size:.8rem;" class="text-muted mt-1">
            <i class="bi bi-layers me-1"></i><span class="fw-semibold">${escHtml(c.coatName || 'Coat')}</span>${color ? ` — ${color}${code}` : ''}${orderBadge}${noColorHint}${coatNotes}
        </div>`;
    }).join('');

    const prepHtml = (item.prepServices || []).length > 0
        ? `<div style="font-size:.8rem;" class="text-muted mt-1">
               <i class="bi bi-tools me-1"></i>${(item.prepServices).map(ps =>
                   `${escHtml(ps.prepServiceName || 'Prep')}${ps.estimatedMinutes ? ` (${ps.estimatedMinutes} min)` : ''}`
               ).join(' · ')}
           </div>`
        : '';

    const notesHtml = item.notes
        ? `<div style="font-size:.8rem;" class="text-muted mt-1">
               <i class="bi bi-sticky me-1"></i>${escHtml(item.notes)}
           </div>`
        : '';

    const unitPrice = item.isGenericItem
        ? `$${fmtNum(item.manualUnitPrice)}`
        : `<span class="card-unit-price text-muted fst-italic" style="font-size:.8rem;">—</span>`;
    const totalPrice = item.isGenericItem
        ? `$${fmtNum((item.manualUnitPrice || 0) * (item.quantity || 1))}`
        : `<span class="card-total-price text-muted fst-italic" style="font-size:.8rem;">—</span>`;

    return `
        <div class="quote-item-card" data-item-index="${i}">
            <div class="d-flex align-items-start gap-2">
                <!-- Description -->
                <div class="flex-grow-1">
                    <div class="d-flex align-items-center flex-wrap gap-1 mb-1">
                        <span class="badge bg-${typeInfo.cls} item-badge">
                            <i class="bi ${typeInfo.icon} me-1"></i>${typeInfo.label}
                        </span>
                        <span class="fw-semibold">${escHtml(item.description || '(Product from catalog)')}</span>
                        ${coatBadge}${complexityBadge}
                    </div>
                    ${coatsHtml}
                    ${prepHtml}
                    ${notesHtml}
                </div>
                <!-- Qty -->
                <div class="text-center flex-shrink-0" style="min-width:45px;">
                    <div class="text-muted" style="font-size:.7rem;">QTY</div>
                    <div class="fw-semibold">${item.quantity}</div>
                </div>
                <!-- Unit Price -->
                <div class="text-end flex-shrink-0" style="min-width:80px;">
                    <div class="text-muted" style="font-size:.7rem;">UNIT PRICE</div>
                    <div class="fw-semibold">${unitPrice}</div>
                </div>
                <!-- Total -->
                <div class="text-end flex-shrink-0" style="min-width:80px;">
                    <div class="text-muted" style="font-size:.7rem;">TOTAL</div>
                    <div class="fw-semibold">${totalPrice}</div>
                </div>
                <!-- Actions -->
                <div class="d-flex gap-1 flex-shrink-0 ms-1">
                    <button type="button" class="btn btn-sm btn-outline-secondary" onclick="openWizard(${i})" title="Edit">
                        <i class="bi bi-pencil"></i>
                    </button>
                    <button type="button" class="btn btn-sm btn-outline-danger" onclick="removeItem(${i})" title="Remove">
                        <i class="bi bi-trash"></i>
                    </button>
                </div>
            </div>
        </div>`;
}

function removeItem(i) {
    quoteItems.splice(i, 1);
    writeHiddenFields();
    renderAllCards();
    scheduleAutoPricing();
}

// ─── Hidden field writer ──────────────────────────────────────────────────────

function writeHiddenFields() {
    const container = document.getElementById('hiddenFieldsContainer');
    if (!container) return;

    const fields = [];
    quoteItems.forEach((item, i) => {
        const p = `${pageMeta.itemsFieldPrefix || 'QuoteItems'}[${i}]`;
        fields.push(h(p + '.Description',        item.description || ''));
        fields.push(h(p + '.Quantity',           item.quantity));
        fields.push(h(p + '.SurfaceAreaSqFt',   item.surfaceAreaSqFt || 0));
        fields.push(h(p + '.EstimatedMinutes',  item.estimatedMinutes || 0));
        fields.push(h(p + '.IsGenericItem',     item.isGenericItem ? 'true' : 'false'));
        fields.push(h(p + '.IsLaborItem',       item.isLaborItem   ? 'true' : 'false'));
        fields.push(h(p + '.IsSalesItem',       item.isSalesItem   ? 'true' : 'false'));
        if (item.sku) fields.push(h(p + '.Sku', item.sku));
        if (item.isSalesItem && item.salesCatalogItemId) fields.push(h(p + '.CatalogItemId', item.salesCatalogItemId));
        fields.push(h(p + '.RequiresSandblasting', 'false'));
        fields.push(h(p + '.RequiresMasking',   'false'));

        if (item.catalogItemId)     fields.push(h(p + '.CatalogItemId',     item.catalogItemId));
        if (item.manualUnitPrice != null) fields.push(h(p + '.ManualUnitPrice', item.manualUnitPrice));
        if (item.powderCostOverride != null) fields.push(h(p + '.PowderCostOverride', item.powderCostOverride));
        if (item.notes)             fields.push(h(p + '.Notes',             item.notes));

        fields.push(h(p + '.IncludePrepCost', item.includePrepCost ? 'true' : 'false'));
        fields.push(h(p + '.Complexity', item.complexity || 'Simple'));
        if (item.isAiItem)   fields.push(h(p + '.IsAiItem', 'true'));
        if (item.aiTags) fields.push(h(p + '.AiTags', item.aiTags));
        if (item.aiPredictionId != null) fields.push(h(p + '.AiPredictionId', item.aiPredictionId));
        if (item.isCustomFormulaItem) {
            fields.push(h(p + '.IsCustomFormulaItem', 'true'));
            if (item.customItemTemplateId != null) fields.push(h(p + '.CustomItemTemplateId', item.customItemTemplateId));
            if (item.formulaFieldValuesJson) fields.push(h(p + '.FormulaFieldValuesJson', item.formulaFieldValuesJson));
        }

        (item.prepServices || []).forEach((ps, pi) => {
            const pp = `${p}.PrepServices[${pi}]`;
            fields.push(h(pp + '.PrepServiceId',    ps.prepServiceId));
            fields.push(h(pp + '.EstimatedMinutes', ps.estimatedMinutes || 0));
            if (ps.blastSetupId != null) fields.push(h(pp + '.BlastSetupId', ps.blastSetupId));
        });

        (item.coats || []).forEach((coat, ci) => {
            const cp = `${p}.Coats[${ci}]`;
            fields.push(h(cp + '.CoatName',          coat.coatName || `Coat ${ci + 1}`));
            fields.push(h(cp + '.Sequence',          coat.sequence || (ci + 1)));
            fields.push(h(cp + '.CoverageSqFtPerLb', coat.coverageSqFtPerLb || 30));
            fields.push(h(cp + '.TransferEfficiency', coat.transferEfficiency || 65));

            if (coat.inventoryItemId)  fields.push(h(cp + '.InventoryItemId',  coat.inventoryItemId));
            if (coat.colorName)        fields.push(h(cp + '.ColorName',        coat.colorName));
            if (coat.colorCode)        fields.push(h(cp + '.ColorCode',        coat.colorCode));
            if (coat.finish)           fields.push(h(cp + '.Finish',           coat.finish));
            if (coat.supplierId)       fields.push(h(cp + '.VendorId',         coat.supplierId));
            if (coat.powderCostPerLb != null) fields.push(h(cp + '.PowderCostPerLb', coat.powderCostPerLb));
            if (coat.powderToOrder)    fields.push(h(cp + '.PowderToOrder',    coat.powderToOrder));
            if (coat.notes)            fields.push(h(cp + '.Notes',            coat.notes));
            fields.push(h(cp + '.NoExtraLayerCharge', coat.noExtraLayerCharge ? 'true' : 'false'));
            if (coat.catalogItemId)    fields.push(h(cp + '.CatalogItemId',    coat.catalogItemId));
            if (coat.addAsIncoming)    fields.push(h(cp + '.AddAsIncoming',    'true'));
        });
    });

    // When a Custom Powder Order is pending (preview visible), include it as a submitted item
    // so the server uses this price (user's override or auto amount) and skips auto-creation.
    if (_customPowderAutoAmount > 0) {
        const amount = _customPowderUserAmount ?? _customPowderAutoAmount;
        const n = quoteItems.length;
        const prefix = pageMeta.itemsFieldPrefix || 'QuoteItems';
        const p = `${prefix}[${n}]`;
        fields.push(h(p + '.Description',        _customPowderPreviewLabel));
        fields.push(h(p + '.Quantity',           1));
        fields.push(h(p + '.IsGenericItem',      'true'));
        fields.push(h(p + '.ManualUnitPrice',    amount.toFixed(2)));
        fields.push(h(p + '.SurfaceAreaSqFt',   0));
        fields.push(h(p + '.EstimatedMinutes',  0));
        fields.push(h(p + '.IsLaborItem',       'false'));
        fields.push(h(p + '.IsSalesItem',       'false'));
        fields.push(h(p + '.RequiresSandblasting', 'false'));
        fields.push(h(p + '.RequiresMasking',   'false'));
        fields.push(h(p + '.IncludePrepCost',   'false'));
        fields.push(h(p + '.Complexity',        'Simple'));
    }

    container.innerHTML = fields.join('');

    // Write all AI photo tempIds as top-level form fields for photo promotion on save
    const aiContainer = document.getElementById('aiPhotoTempIdsContainer');
    if (aiContainer) {
        const allTempIds = quoteItems.flatMap(item => item.aiPhotoTempIds || []);
        aiContainer.innerHTML = allTempIds.map((tid, i) => h(`AiPhotoTempIds[${i}]`, tid)).join('');
    }
}

// Build a hidden input element string
function h(name, value) {
    return `<input type="hidden" name="${escAttr(name)}" value="${escAttr(String(value))}">`;
}

// ─── Auto-pricing ─────────────────────────────────────────────────────────────

let _autoPricingTimer = null;

function scheduleAutoPricing() {
    clearTimeout(_autoPricingTimer);
    _autoPricingTimer = setTimeout(runAutoPricing, 600);
}

async function runAutoPricing() {
    if (quoteItems.length === 0) {
        resetPricingDisplay();
        return;
    }

    const spinner = document.getElementById('pricingSpinner');
    if (spinner) spinner.classList.remove('d-none');

    try {
        // Collect current form meta
        const customerId   = parseInt(document.querySelector('[name="CustomerId"]')?.value) || null;
        const _taxField    = document.querySelector('[name="TaxPercent"]');
        const taxPercent   = _taxField ? parseFloat(_taxField.value) : (pageMeta.taxPercent ?? 0);
        const discountType = document.getElementById('discountTypeSelect')?.value || 'None';
        const discountVal  = parseFloat(document.getElementById('discountValueInput')?.value) || 0;
        const isRushJob        = document.getElementById('IsRushJob')?.checked || false;
        const ovenCostId       = parseInt(document.getElementById('OvenCostId')?.value) || null;
        const ovenBatches      = parseInt(document.getElementById('OvenBatches')?.value) || 1;
        const ovenCycleMinutes = parseInt(document.getElementById('OvenCycleMinutes')?.value) || null;

        // When the user has overridden the powder order price, inject it as a virtual
        // Generic item so the server treats it as an existing Custom Powder Order and
        // prices the total at the user's amount (including any added shipping).
        const pricingItems = [...quoteItems];
        _pricingPayloadHadUserPowderItem = false;
        if (_customPowderAutoAmount > 0 && _customPowderUserAmount !== null) {
            pricingItems.push({
                description: _customPowderPreviewLabel,
                isGenericItem: true,
                manualUnitPrice: _customPowderUserAmount,
                quantity: 1,
                coats: []
            });
            _pricingPayloadHadUserPowderItem = true;
        }

        const payload = {
            items:        pricingItems,
            customerId,
            taxPercent,
            discountType,
            discountValue: discountVal,
            isRushJob,
            ovenCostId,
            ovenBatches,
            ovenCycleMinutes
        };

        const url = pageMeta.pricingUrl || '/Quotes/CalculatePricing';
        const resp = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]')?.value || ''
            },
            body: JSON.stringify(payload)
        });

        if (resp.ok) {
            const result = await resp.json();
            updatePricingDisplay(result);
            updateCardPrices(result.itemResults || []);
        }
    } catch (err) {
        console.warn('Auto-pricing failed:', err);
    } finally {
        if (spinner) spinner.classList.add('d-none');
    }
}

function updatePricingDisplay(r) {
    const show = (id, visible) => document.getElementById(id)?.classList.toggle('d-none', !visible);
    const setText = (id, text) => { const el = document.getElementById(id); if (el) el.textContent = text; };

    // Custom Powder Order preview — server returns amount > 0 only when powder must be ordered
    // and no existing Custom Powder Order item is already in the submitted list.
    if (r.customPowderOrderAmount > 0) {
        _customPowderAutoAmount = r.customPowderOrderAmount;
        const colors = r.customPowderOrderColors || [];
        _customPowderPreviewLabel = colors.length > 0
            ? `Custom Powder Order (${colors.join(', ')})`
            : 'Custom Powder Order';
    } else if (!_pricingPayloadHadUserPowderItem) {
        // Server found no powder and we didn't inject a user item — truly none needed
        _customPowderAutoAmount = 0;
        _customPowderUserAmount = null;
    }
    const hasPowderPreview = _customPowderAutoAmount > 0;
    const preview = document.getElementById('customPowderOrderPreview');
    if (preview) {
        preview.classList.toggle('d-none', !hasPowderPreview);
        if (hasPowderPreview) {
            const descEl = document.getElementById('customPowderOrderPreviewDesc');
            if (descEl) descEl.textContent = _customPowderPreviewLabel;
            const priceEl = document.getElementById('customPowderOrderPreviewPrice');
            if (priceEl) priceEl.textContent = 'Material: $' + fmtNum(_customPowderAutoAmount);
            // Only reset the input when user hasn't overridden
            if (_customPowderUserAmount === null) {
                const input = document.getElementById('customPowderOrderPriceInput');
                if (input) input.value = _customPowderAutoAmount.toFixed(2);
            }
        }
    }

    document.getElementById('pricingPlaceholder')?.classList.add('d-none');
    show('itemsSubtotalRow', true);
    setText('itemsSubtotalDisplay', '$' + fmtNum(r.itemsSubtotal));

    const hasOvenCost = r.ovenBatchCost > 0;
    show('ovenBatchCostRow', hasOvenCost);
    if (hasOvenCost) {
        setText('ovenBatchesDisplay', r.ovenBatches);
        setText('ovenCycleMinDisplay', r.ovenCycleMinutes);
        setText('ovenBatchCostDisplay', '$' + fmtNum(r.ovenBatchCost));
    }

    const hasTierDiscount = r.pricingTierDiscountAmount > 0;
    show('pricingTierDiscountRow', hasTierDiscount);
    if (hasTierDiscount) {
        setText('pricingTierDiscountPercentDisplay', fmtNum(r.pricingTierDiscountPercent));
        setText('pricingTierDiscountDisplay', '-$' + fmtNum(r.pricingTierDiscountAmount));
    }

    const hasQuoteDiscount = r.quoteDiscountAmount > 0;
    show('quoteDiscountRow', hasQuoteDiscount);
    if (hasQuoteDiscount) {
        setText('quoteDiscountPercentDisplay', fmtNum(r.quoteDiscountPercent));
        setText('quoteDiscountDisplay', '-$' + fmtNum(r.quoteDiscountAmount));
    }

    show('rushFeeRow', r.rushFee > 0);
    setText('rushFeeDisplay', '$' + fmtNum(r.rushFee));

    const hasShopSupplies = r.shopSuppliesAmount > 0;
    show('shopSuppliesRow', hasShopSupplies);
    if (hasShopSupplies) {
        setText('shopSuppliesPercentDisplay', fmtNum(r.shopSuppliesPercent));
        setText('shopSuppliesDisplay', '$' + fmtNum(r.shopSuppliesAmount));
    }

    show('subtotalRow', true);
    setText('subtotalDisplay', '$' + fmtNum(r.subtotalAfterDiscount || r.subtotalBeforeDiscount));

    show('taxRow', r.taxAmount > 0);
    setText('taxPercentDisplay', fmtNum(r.taxPercent));
    setText('taxDisplay', '$' + fmtNum(r.taxAmount));

    document.getElementById('pricingDivider')?.classList.remove('d-none');
    show('totalRow', true);
    setText('totalDisplay', '$' + fmtNum(r.total));
}

function updateCardPrices(itemResults) {
    itemResults.forEach((r, i) => {
        const card = document.querySelector(`#itemCardsContainer [data-item-index="${i}"]`);
        if (!card) return;
        const priceEl = card.querySelector('.card-unit-price');
        if (priceEl) priceEl.textContent = '$' + fmtNum(r.unitPrice);
        const totalEl = card.querySelector('.card-total-price');
        if (totalEl) totalEl.textContent = '$' + fmtNum(r.totalPrice);
    });
}

function resetPricingDisplay() {
    const hide = id => document.getElementById(id)?.classList.add('d-none');
    document.getElementById('pricingPlaceholder')?.classList.remove('d-none');
    ['itemsSubtotalRow','ovenBatchCostRow','pricingTierDiscountRow','quoteDiscountRow','rushFeeRow',
     'shopSuppliesRow','subtotalRow','taxRow','pricingDivider','totalRow'].forEach(hide);
    document.getElementById('customPowderOrderPreview')?.classList.add('d-none');
    _customPowderAutoAmount = 0;
    _customPowderUserAmount = null;
}

/// Called when the user edits the Custom Powder Order price input.
/// Stores the override and re-runs pricing so the total reflects the new amount.
function onCustomPowderPriceEdit(val) {
    const v = parseFloat(val);
    _customPowderUserAmount = (!val || isNaN(v) || v <= 0) ? null : v;
    scheduleAutoPricing();
}

// ─── Wizard UI helpers ────────────────────────────────────────────────────────

function updateWizardTitle() {
    const titleEl = document.getElementById('wizardTitle');
    if (titleEl) titleEl.textContent = wz.editIndex >= 0 ? 'Edit Item' : 'Add Item';
}

function updateWizardButtons() {
    const btnBack = document.getElementById('btnWizardBack');
    const btnNext = document.getElementById('btnWizardNext');
    const btnSave = document.getElementById('btnWizardSave');

    if (!btnBack || !btnNext || !btnSave) return;

    const isLast = (wz.step === 4 && wz.itemType !== 'calculated' && wz.itemType !== 'ai')
        || (wz.step === 3 && wz.itemType === 'generic' && wz.data.genericHasCoatings)
        || (wz.step === 2 && (wz.itemType === 'labor' || wz.itemType === 'sales'))
        || (wz.step === 2 && wz.itemType === 'generic' && !wz.data.genericHasCoatings);
    const isCatalogStep = wz.step === 5;

    btnBack.classList.toggle('d-none', wz.step === 1);
    btnNext.classList.toggle('d-none', isLast || isCatalogStep);
    btnSave.classList.toggle('d-none', !isLast || isCatalogStep);

    if (isLast) btnSave.textContent = wz.editIndex >= 0 ? '✓ Update Item' : '✓ Add Item';
}

function updateStepDots() {
    const dots = document.querySelectorAll('.wizard-step-dot');
    const step3Dot  = document.getElementById('step3Dot');
    const step4Dot  = document.getElementById('step4Dot');
    const step2Line = document.getElementById('step2Line');
    const step3Line = document.getElementById('step3Line');

    const hasCoats = wz.itemType !== 'labor' && wz.itemType !== 'sales'
        && !(wz.itemType === 'generic' && !wz.data.genericHasCoatings);

    if (step3Dot)  step3Dot.classList.toggle('skip', !hasCoats);
    if (step4Dot)  step4Dot.classList.toggle('skip', !hasCoats);
    if (step2Line) step2Line.style.opacity = hasCoats ? '1' : '0.3';
    if (step3Line) step3Line.style.opacity = hasCoats ? '1' : '0.3';

    dots.forEach(dot => {
        const s = parseInt(dot.dataset.step);
        dot.classList.remove('active', 'done');
        if (s === wz.step) dot.classList.add('active');
        else if (s < wz.step) dot.classList.add('done');
    });

    // Flat-Rate + coatings stops at step 3 (no prep or catalog save); all others follow normal flow
    const totalSteps = (wz.itemType === 'generic' && wz.data.genericHasCoatings) ? 3
        : hasCoats ? 4 : 2;
    const currentDisplay = Math.min(wz.step, totalSteps);
    const label = document.getElementById('wizardStepLabel');
    if (label) label.textContent = wz.step === 5 ? 'Final Step' : `Step ${currentDisplay} of ${totalSteps}`;

    const stepTitles = {
        1: 'Choose Item Type',
        2: 'Tell Me About The Item',
        3: 'Setup Coating Layers',
        4: 'Select Preparation Services Needed',
        5: 'Save to Product Catalog?'
    };
    const titleEl = document.getElementById('wizardStepTitle');
    if (titleEl) titleEl.textContent = stepTitles[wz.step] || '';
}

// ─── Inline Area Calculator ───────────────────────────────────────────────────

function wzToggleCalc() {
    const panel = document.getElementById('wz_calcPanel');
    if (!panel) return;
    const opening = panel.classList.toggle('d-none') === false; // true when now visible
    if (opening) wzCalcCompute();
}

function wzCalcShape() {
    const shape = document.getElementById('wz_calcShape')?.value || 'rectangle';
    document.getElementById('wz_calcRect')?.classList.toggle('d-none', shape !== 'rectangle');
    document.getElementById('wz_calcCyl')?.classList.toggle('d-none',  shape !== 'cylinder');
    document.getElementById('wz_calcCirc')?.classList.toggle('d-none', shape !== 'circle');
    wzCalcCompute();
}

function wzCalcCompute() {
    const shape = document.getElementById('wz_calcShape')?.value || 'rectangle';
    const isMetric = (pageMeta.areaUnit || 'sq ft').toLowerCase().includes('m');
    let area = 0;

    if (shape === 'rectangle') {
        const l = parseFloat(document.getElementById('wz_calcL')?.value) || 0;
        const w = parseFloat(document.getElementById('wz_calcW')?.value) || 0;
        area = l * w;
    } else if (shape === 'cylinder') {
        const d = parseFloat(document.getElementById('wz_calcD')?.value) || 0;
        const h = parseFloat(document.getElementById('wz_calcH')?.value) || 0;
        const r = d / 2;
        area = 2 * Math.PI * r * r + 2 * Math.PI * r * h;
    } else if (shape === 'circle') {
        const d = parseFloat(document.getElementById('wz_calcCD')?.value) || 0;
        const r = d / 2;
        area = Math.PI * r * r;
    }

    // sq inches → sq ft  /  sq cm → sq m
    const result = isMetric ? area / 10000 : area / 144;
    const el = document.getElementById('wz_calcResult');
    if (el) el.textContent = result.toFixed(3);
    return result;
}

function wzCalcApply() {
    const result = wzCalcCompute();
    const input = document.getElementById('wz_surfaceAreaSqFt');
    if (input) {
        input.value = result.toFixed(3);
        input.dispatchEvent(new Event('input'));
    }
    document.getElementById('wz_calcPanel')?.classList.add('d-none');
}

// ─── Utilities ────────────────────────────────────────────────────────────────

// Scroll an element into view, respecting modal-dialog-scrollable containers
function wzScrollIntoView(el) {
    const modalBody = el.closest('.modal-body');
    if (modalBody) {
        // Use setTimeout to let the browser finish laying out the newly-visible element
        setTimeout(() => modalBody.scrollTop = modalBody.scrollHeight, 0);
    } else {
        el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
}

// ─── Save-to-Catalog step (step 5) ───────────────────────────────────────────

function renderStep5Html() {
    const isAi       = wz.itemType === 'ai';
    const aiDesc     = wz.ai.result?.description || '';
    const prefillName = isAi ? aiDesc : (wz.data.description || '');
    const prefillDesc = isAi ? aiDesc : '';
    const isCalc = wz.itemType === 'calculated';
    const prefillPrice = (wz.data.manualUnitPrice != null && wz.data.manualUnitPrice > 0)
        ? Number(wz.data.manualUnitPrice).toFixed(2)
        : '';
    const priceNote = (isAi || isCalc)
        ? '<div class="text-muted small mb-1">Pre-filled from your item specs — adjust if needed for catalog use.</div>'
        : '<div class="text-muted small mb-1">The system will calculate the final price when the item is added — enter a standard fixed price for catalog use.</div>';

    // Detect sandblasting / masking from selected prep services
    const selectedPrep = wz.data.prepServices || [];
    const hasSandblasting = selectedPrep.some(p => {
        const ps = prepServiceData.find(s => s.id === p.prepServiceId);
        return /sandblast|blast/i.test(ps?.name || '');
    });
    const hasMasking = selectedPrep.some(p => {
        const ps = prepServiceData.find(s => s.id === p.prepServiceId);
        return /mask|tap/i.test(ps?.name || '');
    });

    return `
        <div class="text-center mb-4 pt-2">
            <div class="mb-2" style="font-size:2.5rem;color:var(--bs-success);">
                <i class="bi bi-bookmark-star-fill"></i>
            </div>
            <h6 class="fw-semibold mb-1">Save to Product Catalog?</h6>
            <p class="text-muted small mb-0">
                Add this item to your catalog for quick reuse on future quotes and jobs.
            </p>
        </div>

        <div class="card border-0 bg-body-secondary rounded-3 p-3 mb-3">
            <div class="mb-3">
                <label class="form-label fw-semibold small mb-1">
                    Catalog Item Name <span class="text-danger">*</span>
                </label>
                <input type="text" class="form-control" id="wz_cat_name" maxlength="200"
                       value="${escAttr(prefillName)}" placeholder="e.g. Steel Motorcycle Frame">
                <div class="text-danger small d-none mt-1" id="wz_cat_nameError">Name is required.</div>
            </div>

            <div class="mb-3">
                <label class="form-label fw-semibold small mb-1">
                    Category <span class="text-danger">*</span>
                </label>
                <select class="form-select" id="wz_cat_categoryId">
                    <option value="">Loading categories…</option>
                </select>
                <div class="text-danger small d-none mt-1" id="wz_cat_catError">Please select a category.</div>
            </div>

            <div class="mb-3">
                <label class="form-label fw-semibold small mb-1">Default Price</label>
                ${priceNote}
                <div class="input-group">
                    <span class="input-group-text">$</span>
                    <input type="number" class="form-control" id="wz_cat_price"
                           min="0" step="0.01" value="${escAttr(prefillPrice)}">
                </div>
            </div>

            <div class="mb-3">
                <label class="form-label fw-semibold small mb-1">
                    Description <span class="text-muted fw-normal">(optional)</span>
                </label>
                <textarea class="form-control" id="wz_cat_description" rows="2"
                          placeholder="Brief description of this item">${escHtml(prefillDesc)}</textarea>
            </div>

            <div class="d-flex flex-wrap gap-3">
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="wz_cat_sandblasting"
                           ${hasSandblasting ? 'checked' : ''}>
                    <label class="form-check-label small" for="wz_cat_sandblasting">
                        Typically requires sandblasting
                    </label>
                </div>
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="wz_cat_masking"
                           ${hasMasking ? 'checked' : ''}>
                    <label class="form-check-label small" for="wz_cat_masking">
                        Typically requires masking
                    </label>
                </div>
            </div>
        </div>

        <div class="d-flex gap-2 justify-content-end">
            <button type="button" class="btn btn-outline-secondary" onclick="skipCatalogSave()">
                Skip — Add to Quote Only
            </button>
            <button type="button" class="btn btn-success" id="wz_cat_saveBtn"
                    onclick="saveToCatalogFromWizard()">
                <i class="bi bi-bookmark-plus me-1"></i>Save to Catalog &amp; Add
            </button>
        </div>`;
}

async function loadCatalogCategoriesForWizard() {
    const select = document.getElementById('wz_cat_categoryId');
    if (!select) return;
    try {
        const resp = await fetch('/ItemWizard/GetCatalogCategories');
        const cats = await resp.json();
        if (!cats.length) {
            select.innerHTML = '<option value="">No categories found — add one in Catalog Settings</option>';
            return;
        }
        select.innerHTML = '<option value="">— Select Category —</option>' +
            cats.map(c => `<option value="${c.id}">${escHtml(c.name)}</option>`).join('');
    } catch {
        select.innerHTML = '<option value="">Failed to load — please try again</option>';
    }
}

/// <summary>
/// For calculated items arriving at the Save-to-Catalog step, there is no client-side price yet
/// (pricing happens server-side on form submit). This function calls the same AiRecalcPrice
/// endpoint used by the AI wizard path to fetch an estimated unit price from the item's
/// surface area, minutes, complexity, and coat count, then pre-fills the catalog price field.
/// </summary>
async function prefillCatalogPriceFromCalc() {
    const priceInput = document.getElementById('wz_cat_price');
    if (!priceInput) return;

    const coatCount = (wz.data.coats || []).length || 1;
    const recalcUrl = pageMeta.aiRecalcUrl || '/Quotes/AiRecalcPrice';
    const csrf = document.querySelector('input[name="__RequestVerificationToken"]')?.value || '';

    priceInput.placeholder = 'Calculating…';
    try {
        const resp = await fetch(recalcUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': csrf },
            body: JSON.stringify({
                surfaceAreaSqFt:  wz.data.surfaceAreaSqFt  || 0,
                estimatedMinutes: wz.data.estimatedMinutes || 0,
                complexity:       wz.data.complexity       || 'Moderate',
                coatCount
            })
        });
        const result = await resp.json();
        if (result.success && result.unitPrice > 0) {
            priceInput.value = result.unitPrice.toFixed(2);
        }
    } catch (_) { /* silent — user can type manually */ }
    priceInput.placeholder = '0.00';
}

async function saveToCatalogFromWizard() {
    const name  = document.getElementById('wz_cat_name')?.value?.trim();
    const catId = document.getElementById('wz_cat_categoryId')?.value;

    let valid = true;
    if (!name) {
        document.getElementById('wz_cat_nameError')?.classList.remove('d-none');
        valid = false;
    } else {
        document.getElementById('wz_cat_nameError')?.classList.add('d-none');
    }
    if (!catId) {
        document.getElementById('wz_cat_catError')?.classList.remove('d-none');
        valid = false;
    } else {
        document.getElementById('wz_cat_catError')?.classList.add('d-none');
    }
    if (!valid) return;

    const btn = document.getElementById('wz_cat_saveBtn');
    if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving…'; }

    const payload = {
        name,
        categoryId:                parseInt(catId),
        defaultPrice:              parseFloat(document.getElementById('wz_cat_price')?.value) || 0,
        description:               document.getElementById('wz_cat_description')?.value?.trim() || null,
        approximateArea:           wz.data.surfaceAreaSqFt || 0,
        defaultEstimatedMinutes:   wz.data.estimatedMinutes || 0,
        defaultRequiresSandblasting: document.getElementById('wz_cat_sandblasting')?.checked ?? false,
        defaultRequiresMasking:      document.getElementById('wz_cat_masking')?.checked ?? false
    };

    try {
        const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value || '';
        const resp  = await fetch('/ItemWizard/SaveToCatalog', {
            method:  'POST',
            headers: { 'Content-Type': 'application/json', 'RequestVerificationToken': token },
            body:    JSON.stringify(payload)
        });
        const result = await resp.json();
        if (result.ok) {
            // Show brief success state then finalise the item as normal
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-check-lg me-1"></i>Saved!'; }
            setTimeout(() => wizardSave(), 800);
        } else {
            if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-bookmark-plus me-1"></i>Save to Catalog &amp; Add'; }
            alert(result.error || 'Could not save to catalog. Please try again.');
        }
    } catch {
        if (btn) { btn.disabled = false; btn.innerHTML = '<i class="bi bi-bookmark-plus me-1"></i>Save to Catalog &amp; Add'; }
        alert('Network error saving to catalog. Please try again.');
    }
}

function skipCatalogSave() {
    wizardSave();
}

function escHtml(str) {
    if (!str) return '';
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

function escAttr(str) {
    if (!str) return '';
    return String(str).replace(/&/g,'&amp;').replace(/"/g,'&quot;');
}

function fmtNum(n) {
    return (parseFloat(n) || 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

// ─── Template loading ─────────────────────────────────────────────────────────

/**
 * Pre-populates the item list from a job template.
 * Called from the Jobs/Create view when a templateId is passed in the URL.
 */
function loadItemsFromTemplate(templateItems) {
    if (!templateItems || !templateItems.length) return;

    quoteItems = templateItems.map(ti => ({
        description:          ti.description || null,
        quantity:             ti.quantity || 1,
        surfaceAreaSqFt:      ti.surfaceAreaSqFt || 0,
        estimatedMinutes:     ti.estimatedMinutes || 0,
        catalogItemId:        ti.catalogItemId || null,
        manualUnitPrice:      ti.manualUnitPrice ?? null,
        powderCostOverride:   null,
        isGenericItem:        !!ti.isGenericItem,
        isLaborItem:          !!ti.isLaborItem,
        isAiItem:             false,
        isCustomFormulaItem:  false,
        customItemTemplateId: null,
        formulaFieldValuesJson: null,
        requiresSandblasting: !!ti.requiresSandblasting,
        requiresMasking:      !!ti.requiresMasking,
        notes:                null,
        complexity:           ti.complexity || 'Simple',
        includePrepCost:      ti.includePrepCost ?? true,
        aiPhotoTempIds:       [],
        aiPhotoFileNames:     [],
        aiTags:               null,
        aiPredictionId:       null,
        coats: (ti.coats || []).map(c => ({
            coatName:           c.coatName || 'Coat',
            sequence:           c.sequence || 1,
            powderType:         c.inventoryItemId ? 'stock' : 'custom',
            inventoryItemId:    c.inventoryItemId || null,
            colorName:          c.colorName || null,
            colorCode:          c.colorCode || null,
            finish:             c.finish || null,
            coverageSqFtPerLb:  c.coverageSqFtPerLb || 30,
            transferEfficiency: c.transferEfficiency || 65,
            powderCostPerLb:    c.powderCostPerLb || null,
            noExtraLayerCharge: !!c.noExtraLayerCharge
        })),
        prepServices: (ti.prepServices || []).map(p => ({
            prepServiceId:   p.prepServiceId,
            prepServiceName: p.prepServiceName || '',
            estimatedMinutes: p.estimatedMinutes || 0
        }))
    }));

    writeHiddenFields();
    renderAllCards();
    scheduleAutoPricing();
}

// ── Surface area calculator modal ─────────────────────────────────────────────
let _sqFtTargetInput = null;

function openSqFtCalculator(inputId) {
    _sqFtTargetInput = inputId;
    document.getElementById('rectLength').value = 0;
    document.getElementById('rectWidth').value  = 0;
    document.getElementById('calcResult').textContent = '0.00';
    new bootstrap.Modal(document.getElementById('sqFtCalculatorModal')).show();
}

function toggleShapeInputs() {
    const shape = document.getElementById('calcShape').value;
    document.getElementById('rectangleInputs').style.display = shape === 'rectangle' ? 'block' : 'none';
    document.getElementById('cylinderInputs').style.display  = shape === 'cylinder'  ? 'block' : 'none';
    document.getElementById('circleInputs').style.display    = shape === 'circle'    ? 'block' : 'none';
    calculateSqFt();
}

function calculateSqFt() {
    const useMetric = !!(pageMeta && pageMeta.useMetric);
    const divisor = useMetric ? 10000 : 144;
    const shape = document.getElementById('calcShape').value;
    let result = 0;
    if (shape === 'rectangle') {
        const l = parseFloat(document.getElementById('rectLength').value) || 0;
        const w = parseFloat(document.getElementById('rectWidth').value) || 0;
        result = (l * w) / divisor;
    } else if (shape === 'cylinder') {
        const d = parseFloat(document.getElementById('cylDiameter').value) || 0;
        const h = parseFloat(document.getElementById('cylHeight').value) || 0;
        const r = d / 2;
        result = (2 * Math.PI * r * r + 2 * Math.PI * r * h) / divisor;
    } else {
        const d = parseFloat(document.getElementById('circDiameter').value) || 0;
        const r = d / 2;
        result = (Math.PI * r * r) / divisor;
    }
    document.getElementById('calcResult').textContent = result.toFixed(4);
}

function useSqFtResult() {
    const val = document.getElementById('calcResult').textContent;
    if (_sqFtTargetInput) {
        const el = document.getElementById(_sqFtTargetInput) || document.querySelector(`[name="${_sqFtTargetInput}"]`);
        if (el) { el.value = parseFloat(val).toFixed(2); el.dispatchEvent(new Event('change')); }
    }
    bootstrap.Modal.getInstance(document.getElementById('sqFtCalculatorModal'))?.hide();
}
