/* oven-scheduler.js — Batch scheduler with mouse-event drag (no HTML5 DnD API) */

'use strict';

// ──────────────────────────────────────────────────────────────────────────────
// Drag state
// ──────────────────────────────────────────────────────────────────────────────
let dragState = null;
/*  dragState shape:
    {
        type: 'queue' | 'batch',
        sourceEl: HTMLElement,          // original row being dragged
        ghost: HTMLElement,             // floating clone following the cursor
        offsetX, offsetY: number,       // cursor offset within the source element
        // queue-specific:
        coatId, jobId, jobItemId, sqft, color, colorCode,
        pass, coatName, jobNumber, customer, description, priority, dueDate
        // batch-specific:
        batchItemId, sourceBatchId, sqft
    }
*/

// Currently highlighted batch card id (for visual feedback)
let dragOverBatchId = null;

// ──────────────────────────────────────────────────────────────────────────────
// Init — wire up mousedown on all draggable rows
// ──────────────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
    initDraggable();
});

function initDraggable() {
    // Queue coat rows
    document.querySelectorAll('[data-coat-id]').forEach(el => {
        el.addEventListener('mousedown', onQueueMouseDown);
    });

    // Batch item rows (only those with draggable=true, i.e. editable batches)
    document.querySelectorAll('[data-batch-item-id]').forEach(el => {
        if (el.dataset.draggable === 'true' || el.getAttribute('draggable') === 'true') {
            el.addEventListener('mousedown', onBatchItemMouseDown);
        }
    });
}

// ──────────────────────────────────────────────────────────────────────────────
// Mousedown handlers
// ──────────────────────────────────────────────────────────────────────────────
function onQueueMouseDown(e) {
    if (e.button !== 0) return;               // left button only
    if (e.target.closest('button')) return;   // don't intercept button clicks

    const el = e.currentTarget;
    const rect = el.getBoundingClientRect();

    dragState = {
        type: 'queue',
        sourceEl: el,
        offsetX: e.clientX - rect.left,
        offsetY: e.clientY - rect.top,
        coatId:      +el.dataset.coatId,
        jobId:       +el.dataset.jobId,
        jobItemId:   +el.dataset.jobItemId,
        sqft:        +el.dataset.sqft,
        color:        el.dataset.color,
        colorCode:    el.dataset.colorCode,
        pass:        +el.dataset.pass,
        coatName:     el.dataset.coatName,
        jobNumber:    el.dataset.jobNumber,
        customer:     el.dataset.customer,
        description:  el.dataset.description,
        priority:     el.dataset.priority,
        dueDate:      el.dataset.dueDate
    };

    startDrag(el, e);
    e.preventDefault();
}

function onBatchItemMouseDown(e) {
    if (e.button !== 0) return;
    if (e.target.closest('button')) return;

    const el = e.currentTarget;
    const rect = el.getBoundingClientRect();

    dragState = {
        type: 'batch',
        sourceEl: el,
        offsetX: e.clientX - rect.left,
        offsetY: e.clientY - rect.top,
        batchItemId:   +el.dataset.batchItemId,
        sourceBatchId: +el.dataset.batchId,
        sqft:          +el.dataset.sqft
    };

    startDrag(el, e);
    e.preventDefault();
}

// ──────────────────────────────────────────────────────────────────────────────
// Ghost element
// ──────────────────────────────────────────────────────────────────────────────
function startDrag(sourceEl, e) {
    const rect = sourceEl.getBoundingClientRect();

    const ghost = sourceEl.cloneNode(true);
    ghost.style.cssText = `
        position: fixed;
        left: ${rect.left}px;
        top: ${rect.top}px;
        width: ${rect.width}px;
        pointer-events: none;
        z-index: 9999;
        opacity: 0.85;
        box-shadow: 0 8px 24px rgba(0,0,0,.25);
        border-radius: 6px;
        background: var(--bs-body-bg);
        border: 1px solid var(--bs-border-color);
        padding: .3rem .5rem;
        font-size: .82rem;
        transition: none;
    `;

    document.body.appendChild(ghost);
    dragState.ghost = ghost;

    sourceEl.style.opacity = '0.35';
}

function moveGhost(e) {
    if (!dragState?.ghost) return;
    dragState.ghost.style.left = (e.clientX - dragState.offsetX) + 'px';
    dragState.ghost.style.top  = (e.clientY - dragState.offsetY) + 'px';
}

// ──────────────────────────────────────────────────────────────────────────────
// Document mousemove — move ghost and highlight drop targets
// ──────────────────────────────────────────────────────────────────────────────
document.addEventListener('mousemove', e => {
    if (!dragState) return;
    moveGhost(e);

    // Hide ghost temporarily so elementFromPoint sees what's underneath
    dragState.ghost.style.display = 'none';
    const elUnder = document.elementFromPoint(e.clientX, e.clientY);
    dragState.ghost.style.display = '';

    const batchCard = elUnder?.closest('.batch-card');
    const newId = batchCard ? +batchCard.dataset.batchId : null;

    if (newId !== dragOverBatchId) {
        // Remove old highlight
        if (dragOverBatchId !== null) {
            document.getElementById(`batch-${dragOverBatchId}`)?.classList.remove('drag-over');
            document.getElementById(`empty-items-${dragOverBatchId}`)?.classList.remove('drag-over');
        }
        // Add new highlight
        if (newId !== null) {
            batchCard.classList.add('drag-over');
            document.getElementById(`empty-items-${newId}`)?.classList.add('drag-over');
        }
        dragOverBatchId = newId;
    }

    // Highlight empty oven zone when a queue item hovers over it
    const ovenZone = elUnder?.closest('[data-oven-id]');
    document.querySelectorAll('.drop-zone-empty').forEach(z => z.classList.remove('drag-over'));
    if (dragState.type === 'queue' && !batchCard && ovenZone) {
        document.getElementById(`empty-${ovenZone.dataset.ovenId}`)?.classList.add('drag-over');
    }

    // Highlight queue when a batch item is being dragged (indicates it can be dropped there)
    const queueContainer = document.getElementById('queueContainer');
    const overQueue = elUnder?.closest('#queueContainer') || elUnder?.closest('.scheduler-queue');
    if (dragState.type === 'batch') {
        queueContainer?.classList.toggle('drag-over', !!overQueue);
    } else {
        queueContainer?.classList.remove('drag-over');
    }
});

// ──────────────────────────────────────────────────────────────────────────────
// Document mouseup — commit the drop
// ──────────────────────────────────────────────────────────────────────────────
document.addEventListener('mouseup', e => {
    if (!dragState) return;
    if (e.button !== 0) return;


    // Clear all highlights
    if (dragOverBatchId !== null) {
        document.getElementById(`batch-${dragOverBatchId}`)?.classList.remove('drag-over');
        document.getElementById(`empty-items-${dragOverBatchId}`)?.classList.remove('drag-over');
    }
    document.getElementById('queueContainer')?.classList.remove('drag-over');
    document.querySelectorAll('.drop-zone-empty').forEach(z => z.classList.remove('drag-over'));

    // Restore source element
    if (dragState.sourceEl) dragState.sourceEl.style.opacity = '';

    // Remove ghost
    dragState.ghost?.remove();

    // Find batch card under cursor
    dragState.ghost = null;
    const elUnder = document.elementFromPoint(e.clientX, e.clientY);
    const batchCard = elUnder?.closest('.batch-card');

    const state = dragState;
    dragState = null;
    dragOverBatchId = null;

    if (!batchCard) {
        if (state.type === 'queue') {
            // Dropped on empty oven zone — auto-create batch and add
            const ovenZone = elUnder?.closest('[data-oven-id]');
            if (ovenZone) autoCreateBatchAndAdd(+ovenZone.dataset.ovenId, state);
        } else if (state.type === 'batch') {
            // Dropped outside any batch card — return item to queue
            removeFromBatch(state.batchItemId, state.sourceBatchId);
        }
        return;
    }
    const batchId = +batchCard.dataset.batchId;

    if (state.type === 'queue') {
        addCoatToBatch(state, batchId);
    } else if (state.type === 'batch' && state.sourceBatchId !== batchId) {
        moveToBatch(state.batchItemId, batchId, state.sourceBatchId, state.sqft);
    }
});

// ──────────────────────────────────────────────────────────────────────────────
// Add coat from queue to batch
// ──────────────────────────────────────────────────────────────────────────────
async function addCoatToBatch(drag, batchId) {
    const res = await apiPost(URLS.addToBatch, {
        batchId,
        jobItemCoatId: drag.coatId
    });

    if (!res.success) { showToast(res.error || 'Failed to add to batch', 'danger'); return; }

    // Remove from queue
    document.getElementById(`qcoat-${drag.coatId}`)?.remove();

    // Add to batch items list
    appendBatchItemRow(batchId, res.item, true);

    // Update capacity display
    updateBatchCapacity(batchId, res.totalSurfaceAreaSqFt, res.capacityPct);

    // Update drop hint: switch from "Drag coats here" to "Drop more here"
    const hint = document.getElementById(`empty-items-${batchId}`);
    if (hint) {
        hint.classList.remove('batch-drop-hint-empty');
        hint.innerHTML = '<i class="bi bi-plus-circle me-1"></i><span>Drop more here</span>';
    }

    updateQueueCount(-1);
    showToast('Added to batch', 'success');
}

// ──────────────────────────────────────────────────────────────────────────────
// Auto-create a batch then add the dragged coat (dropped on empty oven zone)
// ──────────────────────────────────────────────────────────────────────────────
async function autoCreateBatchAndAdd(ovenCostId, drag) {
    const createRes = await apiPost(URLS.createBatch, {
        ovenCostId,
        scheduledDate: SCHEDULED_DATE,
        scheduledStartTime: null
    });
    if (!createRes.success) { showToast(createRes.error || 'Failed to create batch', 'danger'); return; }

    // Add the coat to the newly created batch before reloading
    const addRes = await apiPost(URLS.addToBatch, {
        batchId: createRes.batch.id,
        jobItemCoatId: drag.coatId
    });
    if (!addRes.success) { showToast(addRes.error || 'Batch created but could not add coat', 'warning'); }
    else { showToast('Batch created and item added!', 'success'); }

    setTimeout(() => window.location.reload(), 600);
}

// ──────────────────────────────────────────────────────────────────────────────
// Move batch item between batches
// ──────────────────────────────────────────────────────────────────────────────
async function moveToBatch(batchItemId, targetBatchId, sourceBatchId, sqft) {
    const res = await apiPost(URLS.moveToBatch, { batchItemId, targetBatchId });
    if (!res.success) { showToast(res.error || 'Failed to move item', 'danger'); return; }

    // Move DOM element
    const itemEl = document.getElementById(`bitem-${batchItemId}`);
    const targetList = document.getElementById(`items-${targetBatchId}`);
    if (itemEl && targetList) {
        itemEl.dataset.batchId = targetBatchId;
        targetList.appendChild(itemEl);
        document.getElementById(`empty-items-${targetBatchId}`)?.remove();
    }

    // Update capacity displays
    const sourceBatchCard = document.getElementById(`batch-${sourceBatchId}`);
    const maxSource = +(sourceBatchCard?.dataset.maxSqft || 0);
    updateBatchCapacity(sourceBatchId, res.sourceBatchTotal,
        maxSource > 0 ? Math.round(res.sourceBatchTotal / maxSource * 1000) / 10 : null);

    const targetBatchCard = document.getElementById(`batch-${targetBatchId}`);
    const maxTarget = +(targetBatchCard?.dataset.maxSqft || 0);
    updateBatchCapacity(targetBatchId, res.targetBatchTotal,
        maxTarget > 0 ? Math.round(res.targetBatchTotal / maxTarget * 1000) / 10 : null);

    showToast('Item moved', 'success');
}

// ──────────────────────────────────────────────────────────────────────────────
// Remove coat from batch back to queue (no page reload)
// ──────────────────────────────────────────────────────────────────────────────
async function removeFromBatch(batchItemId, batchId) {
    const res = await apiPost(URLS.removeFromBatch, { batchItemId });
    if (!res.success) { showToast(res.error || 'Failed to remove', 'danger'); return; }

    document.getElementById(`bitem-${batchItemId}`)?.remove();

    const batchCard = document.getElementById(`batch-${batchId}`);
    const maxSqft = +(batchCard?.dataset.maxSqft || 0);
    const capPct = maxSqft > 0 ? Math.round(res.batchTotal / maxSqft * 1000) / 10 : null;
    updateBatchCapacity(batchId, res.batchTotal, capPct);

    if (res.queueItem) returnCoatToQueue(res.queueItem);

    showToast('Removed — returned to queue', 'info');
}

function returnCoatToQueue(q) {
    const coatElId = `qcoat-${q.jobItemCoatId}`;
    if (document.getElementById(coatElId)) return;

    const borderColors = { Rush: '#dc3545', Urgent: '#fd7e14', High: '#ffc107', Normal: '#0d6efd', Low: '#6c757d' };
    const border = borderColors[q.priority] || '#6c757d';

    const coatEl = document.createElement('div');
    coatEl.className = 'batch-item-row d-flex align-items-center';
    coatEl.id = coatElId;
    coatEl.dataset.coatId      = q.jobItemCoatId;
    coatEl.dataset.jobId       = q.jobId;
    coatEl.dataset.jobItemId   = q.jobItemId;
    coatEl.dataset.sqft        = q.surfaceAreaSqFt;
    coatEl.dataset.color       = q.colorName || '';
    coatEl.dataset.colorCode   = q.colorCode || '';
    coatEl.dataset.pass        = q.coatPassNumber;
    coatEl.dataset.coatName    = q.coatName;
    coatEl.dataset.jobNumber   = q.jobNumber;
    coatEl.dataset.customer    = q.customerName;
    coatEl.dataset.description = q.itemDescription;
    coatEl.dataset.priority    = q.priority;
    coatEl.dataset.dueDate     = q.dueDate || '';

    const colorDot = q.colorName
        ? `<span class="color-dot" style="background:#aaaaaa;"></span>`
        : '';

    coatEl.innerHTML = `
        <div class="flex-grow-1 overflow-hidden">
            <div class="text-truncate">
                ${colorDot}
                <span class="fw-medium">${escHtml(q.coatName)}</span>
                <span class="text-muted ms-1">— ${escHtml(q.itemDescription)}</span>
            </div>
            <div class="text-muted" style="font-size:.75rem;">
                Pass ${q.coatPassNumber} · ${(+q.surfaceAreaSqFt).toFixed(1)} sqft
                ${q.colorName ? `· ${escHtml(q.colorName)}` : ''}
            </div>
        </div>
        <i class="bi bi-grip-vertical text-muted ms-1"></i>
    `;

    // Wire drag via mousedown
    coatEl.addEventListener('mousedown', onQueueMouseDown);

    // Find or create the job card in the queue
    const existingJobCard = document.getElementById(`qjob-${q.jobId}`);
    if (existingJobCard) {
        document.getElementById(`coats-job-${q.jobId}`)?.appendChild(coatEl);
    } else {
        const priorityBadgeClass = { Rush: 'bg-danger', Urgent: 'bg-warning text-dark', High: 'bg-primary', Normal: 'bg-secondary', Low: 'bg-light text-dark' }[q.priority] || 'bg-secondary';
        const dueDateHtml = q.dueDate
            ? `<div class="small ${q.isOverdue ? 'text-danger fw-semibold' : 'text-muted'}">
                <i class="bi bi-calendar-event me-1"></i>Due ${formatDate(q.dueDate)}
                ${q.isOverdue ? '<span class="badge bg-danger ms-1">Overdue</span>' : ''}
               </div>`
            : '';

        const jobCard = document.createElement('div');
        jobCard.className = 'queue-job-card card border-0 shadow-sm';
        jobCard.id = `qjob-${q.jobId}`;
        jobCard.dataset.priority = q.priority;
        jobCard.style.borderLeft = `4px solid ${border}`;
        jobCard.innerHTML = `
            <div class="card-body p-2">
                <div class="d-flex align-items-center mb-1">
                    <span class="fw-semibold small me-auto">${escHtml(q.jobNumber)}</span>
                    <span class="badge ${priorityBadgeClass} ms-1 small">${escHtml(q.priority)}</span>
                </div>
                <div class="text-muted small mb-1">${escHtml(q.customerName)}</div>
                ${dueDateHtml}
                <div class="mt-2" id="coats-job-${q.jobId}"></div>
            </div>
        `;

        const queueContainer = document.getElementById('queueContainer');
        const cards = [...queueContainer.querySelectorAll('.queue-job-card')];
        const insertBefore = cards.find(c => +(c.dataset.priorityId || 0) < +q.priorityId);
        insertBefore ? queueContainer.insertBefore(jobCard, insertBefore) : queueContainer.appendChild(jobCard);

        document.getElementById(`coats-job-${q.jobId}`)?.appendChild(coatEl);
    }

    updateQueueCount(1);
}

function updateQueueCount(delta) {
    const badge = document.getElementById('queueCount');
    if (badge) badge.textContent = Math.max(0, (+badge.textContent || 0) + delta);
}

function formatDate(iso) {
    if (!iso) return '';
    return new Date(iso + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}

// ──────────────────────────────────────────────────────────────────────────────
// Create an empty batch
// ──────────────────────────────────────────────────────────────────────────────
async function createBatch(ovenCostId, ovenName, date) {
    const res = await apiPost(URLS.createBatch, { ovenCostId, scheduledDate: date, scheduledStartTime: null });
    if (!res.success) { showToast(res.error || 'Failed to create batch', 'danger'); return; }
    window.location.reload();
}

// ──────────────────────────────────────────────────────────────────────────────
// Batch lifecycle actions
// ──────────────────────────────────────────────────────────────────────────────
async function startBatch(batchId) {
    if (!await showConfirm('Start this batch? All linked jobs will be updated to "In Oven" status.', 'Start Batch')) return;
    const res = await apiPost(URLS.startBatch, { batchId });
    if (!res.success) { showToast(res.error || 'Failed to start batch', 'danger'); return; }
    showToast('Batch started!', 'success');
    window.location.reload();
}

async function completeBatch(batchId) {
    if (!await showConfirm('Mark this batch as complete? Job statuses will be updated to Curing or Coating.', 'Complete Batch')) return;
    const res = await apiPost(URLS.completeBatch, { batchId });
    if (!res.success) { showToast(res.error || 'Failed to complete batch', 'danger'); return; }
    showToast('Batch completed!', 'success');
    window.location.reload();
}

async function deleteBatch(batchId, batchNumber) {
    if (!await showConfirm(`Delete batch ${batchNumber}? Items will be returned to the queue.`, 'Delete', true)) return;
    const res = await apiPost(URLS.deleteBatch, { batchId });
    if (!res.success) { showToast(res.error || 'Failed to delete batch', 'danger'); return; }

    const card = document.getElementById(`batch-${batchId}`);
    const ovenId = card?.dataset.ovenId;
    card?.remove();

    // If the oven column is now empty, restore the drop zone hint
    const zone = document.getElementById(`zone-${ovenId}`);
    if (zone && !zone.querySelector('.batch-card')) {
        const hint = document.createElement('div');
        hint.className = 'drop-zone-empty';
        hint.id = `empty-${ovenId}`;
        hint.innerHTML = '<span><i class="bi bi-fire me-1"></i>Drop items here or click + to create a batch</span>';
        zone.appendChild(hint);
    }

    showToast('Batch deleted', 'info');
}

// ──────────────────────────────────────────────────────────────────────────────
// AI Suggestion Panel
// ──────────────────────────────────────────────────────────────────────────────
function openAiPanel() {
    document.getElementById('aiPanel').classList.add('open');
    document.getElementById('aiBackdrop').classList.add('open');
    const goalLabels = {
        maximize_throughput: 'Maximize Throughput',
        minimize_lateness: 'Minimize Lateness',
        minimize_color_changes: 'Minimize Color Changes'
    };
    document.getElementById('goalLabel').textContent = goalLabels[OPTIMIZATION_GOAL] || OPTIMIZATION_GOAL;
}

function closeAiPanel() {
    document.getElementById('aiPanel').classList.remove('open');
    document.getElementById('aiBackdrop').classList.remove('open');
}

async function runAiSuggest() {
    showAiState('loading');

    const res = await apiPost(URLS.suggest, { optimizationGoal: OPTIMIZATION_GOAL });

    if (!res.success) {
        document.getElementById('aiErrorText').textContent = res.error || 'AI error';
        showAiState('error');
        return;
    }

    const { suggestion } = res;
    AI_SUGGESTION_DATA.batches = suggestion.batches;

    document.getElementById('aiSummary').textContent = suggestion.summary;

    const warnEl = document.getElementById('aiWarnings');
    if (suggestion.warnings?.length > 0) {
        warnEl.innerHTML = suggestion.warnings.map(w =>
            `<div class="alert alert-warning py-1 px-2 mb-1 small"><i class="bi bi-exclamation-triangle me-1"></i>${escHtml(w)}</div>`
        ).join('');
        warnEl.classList.remove('d-none');
    } else {
        warnEl.classList.add('d-none');
    }

    const listEl = document.getElementById('aiBatchList');
    listEl.innerHTML = '';

    if (!suggestion.batches?.length) {
        listEl.innerHTML = '<div class="text-muted text-center py-3 small">No batches suggested. The queue may be empty or all coats are already scheduled.</div>';
    } else {
        suggestion.batches.forEach((batch, idx) => {
            const capPct = batch.capacityUtilization > 0 ? Math.round(batch.capacityUtilization * 100) : null;
            const capBar = capPct !== null
                ? `<div class="mt-1">
                    <div class="d-flex justify-content-between mb-1" style="font-size:.72rem;">
                        <span class="text-muted">Utilization</span>
                        <span class="fw-semibold">${capPct}%</span>
                    </div>
                    <div class="capacity-bar-wrap">
                        <div class="capacity-bar-fill ${capPct >= 100 ? 'cap-over' : capPct >= 80 ? 'cap-warn' : 'cap-ok'}"
                             style="width:${Math.min(capPct, 100)}%"></div>
                    </div>
                   </div>` : '';

            const items = batch.items.map(i =>
                `<div class="d-flex align-items-center gap-1 py-1 border-bottom" style="font-size:.78rem;">
                    <span class="badge bg-secondary" style="font-size:.68rem;">Pass ${i.coatPassNumber}</span>
                    <div class="flex-grow-1 text-truncate">
                        <span class="fw-medium">${escHtml(i.jobNumber)}</span>
                        <span class="text-muted ms-1">${escHtml(i.description)}</span>
                    </div>
                    <span class="text-muted">${i.surfaceAreaSqFt.toFixed(1)} sqft</span>
                </div>`
            ).join('');

            listEl.innerHTML +=
                `<div class="card mb-3 shadow-sm" id="aibatch-${idx}">
                    <div class="card-header py-2 px-3 d-flex align-items-center gap-2"
                         style="background:linear-gradient(90deg,#6f42c1 0%,#0d6efd 100%);color:white;">
                        <i class="bi bi-fire"></i>
                        <span class="fw-semibold small flex-grow-1">${escHtml(batch.batchName)}</span>
                        <span class="badge bg-white text-dark" style="font-size:.7rem;">${escHtml(batch.ovenName)}</span>
                    </div>
                    <div class="card-body p-2">
                        <div class="d-flex flex-wrap gap-1 mb-2">
                            ${batch.suggestedStartTime ? `<span class="badge bg-light text-dark border"><i class="bi bi-clock me-1"></i>${batch.suggestedStartTime}</span>` : ''}
                            ${batch.primaryColorName ? `<span class="badge bg-secondary"><i class="bi bi-palette me-1"></i>${escHtml(batch.primaryColorName)}</span>` : ''}
                            ${batch.cureTemperatureF ? `<span class="badge bg-danger">${batch.cureTemperatureF}°F</span>` : ''}
                            <span class="badge bg-light text-dark border">${batch.estimatedSqFt.toFixed(1)} sqft</span>
                            <span class="badge bg-light text-dark border">${batch.estimatedCycleMinutes} min</span>
                        </div>
                        ${capBar}
                        <div class="mt-2">${items}</div>
                        <div class="mt-2 text-muted small">
                            <i class="bi bi-chat-left-text me-1" style="color:#6f42c1;"></i>
                            ${escHtml(batch.rationale)}
                        </div>
                    </div>
                </div>`;
        });
    }

    showAiState('results');
}

async function acceptAllBatches() {
    if (!AI_SUGGESTION_DATA.batches?.length) return;

    document.getElementById('btnAcceptAll').disabled = true;
    document.getElementById('btnAcceptAll').innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving...';

    const res = await apiPost(URLS.acceptSuggestion, {
        scheduledDate: SCHEDULED_DATE,
        batches: AI_SUGGESTION_DATA.batches
    });

    if (!res.success) {
        showToast(res.error || 'Failed to save batches', 'danger');
        document.getElementById('btnAcceptAll').disabled = false;
        document.getElementById('btnAcceptAll').innerHTML = '<i class="bi bi-check-all me-1"></i>Accept All Batches';
        return;
    }

    showToast(`${res.batches?.length || 0} batch(es) created!`, 'success');
    closeAiPanel();
    setTimeout(() => window.location.reload(), 600);
}

function showAiState(state) {
    ['loading', 'error', 'results', 'initial'].forEach(s => {
        const el = document.getElementById(`ai${s.charAt(0).toUpperCase() + s.slice(1)}`);
        if (el) {
            if (s === state) {
                el.classList.remove('d-none');
                if (s === 'results') el.classList.add('d-flex', 'flex-column');
            } else {
                el.classList.add('d-none');
                if (s === 'results') el.classList.remove('d-flex', 'flex-column');
            }
        }
    });
}

// ──────────────────────────────────────────────────────────────────────────────
// DOM helpers
// ──────────────────────────────────────────────────────────────────────────────
function appendBatchItemRow(batchId, item, withRemove) {
    const list = document.getElementById(`items-${batchId}`);
    if (!list) return;

    const el = document.createElement('div');
    el.className = 'batch-item-row d-flex align-items-center';
    el.id = `bitem-${item.id}`;
    el.dataset.batchItemId = item.id;
    el.dataset.batchId = batchId;
    el.dataset.sqft = item.surfaceAreaContribution;
    el.setAttribute('draggable', 'true'); // kept for CSS selector compat

    el.innerHTML = `
        <div class="flex-grow-1 overflow-hidden">
            <div class="d-flex align-items-center gap-1 text-truncate">
                <span class="badge bg-light text-dark border" style="font-size:.7rem;">Pass ${item.coatPassNumber}</span>
                ${item.colorName ? `<span class="text-truncate fw-medium small">${escHtml(item.colorName)}</span>` : ''}
                <span class="text-muted small text-truncate">${escHtml(item.itemDescription)}</span>
            </div>
            <div class="text-muted d-flex align-items-center gap-2" style="font-size:.73rem;">
                <span>${escHtml(item.jobNumber)}</span><span>·</span>
                <span>${(+item.surfaceAreaContribution).toFixed(1)} sqft</span>
            </div>
        </div>
        ${withRemove
            ? `<div class="d-flex align-items-center ms-1 gap-1">
                <i class="bi bi-grip-vertical text-muted"></i>
                <button class="btn btn-sm p-0 text-danger" style="line-height:1;"
                        onclick="removeFromBatch(${item.id}, ${batchId})" title="Remove">
                    <i class="bi bi-x"></i>
                </button>
               </div>`
            : '<i class="bi bi-grip-vertical text-muted ms-1"></i>'}
    `;

    // Wire mouse-based drag
    el.addEventListener('mousedown', onBatchItemMouseDown);

    list.appendChild(el);
}

function updateBatchCapacity(batchId, totalSqFt, capPct) {
    document.querySelectorAll(`.batch-sqft[data-batch-id="${batchId}"]`).forEach(el => {
        const maxSqft = document.getElementById(`batch-${batchId}`)?.dataset.maxSqft;
        el.innerHTML = maxSqft
            ? `${(+totalSqFt).toFixed(1)} / ${(+maxSqft).toFixed(0)} sqft`
            : `${(+totalSqFt).toFixed(1)} sqft`;
    });

    document.querySelectorAll(`.batch-cap-bar[data-batch-id="${batchId}"]`).forEach(bar => {
        if (capPct !== null) {
            const pct = Math.min(+capPct, 100);
            bar.style.width = pct + '%';
            bar.className = `capacity-bar-fill ${pct >= 100 ? 'cap-over' : pct >= 80 ? 'cap-warn' : 'cap-ok'} batch-cap-bar`;
            bar.dataset.batchId = batchId;
        }
    });

    const card = document.getElementById(`batch-${batchId}`);
    if (card) card.dataset.totalSqft = totalSqFt;
}

// ──────────────────────────────────────────────────────────────────────────────
// Utility
// ──────────────────────────────────────────────────────────────────────────────
async function apiPost(url, data) {
    try {
        const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value
            || document.querySelector('meta[name="csrf-token"]')?.content;
        const res = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                ...(token ? { 'RequestVerificationToken': token } : {})
            },
            body: JSON.stringify(data)
        });
        return await res.json();
    } catch (e) {
        return { success: false, error: e.message };
    }
}

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

function showConfirm(message, confirmLabel = 'Confirm', danger = false) {
    return new Promise(resolve => {
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position:fixed; inset:0; z-index:10000;
            background:rgba(0,0,0,.45);
            display:flex; align-items:center; justify-content:center;
        `;

        const box = document.createElement('div');
        box.style.cssText = `
            background:var(--bs-body-bg);
            border-radius:12px;
            padding:1.5rem 1.75rem;
            max-width:360px; width:90%;
            box-shadow:0 16px 48px rgba(0,0,0,.3);
            text-align:center;
        `;

        const msg = document.createElement('p');
        msg.style.cssText = 'margin:0 0 1.25rem; font-size:.95rem; line-height:1.5;';
        msg.textContent = message;

        const btnRow = document.createElement('div');
        btnRow.style.cssText = 'display:flex; gap:.75rem; justify-content:center;';

        const btnCancel = document.createElement('button');
        btnCancel.className = 'btn btn-outline-secondary';
        btnCancel.textContent = 'Cancel';

        const btnOk = document.createElement('button');
        btnOk.className = `btn ${danger ? 'btn-danger' : 'btn-primary'}`;
        btnOk.textContent = confirmLabel;

        btnRow.appendChild(btnCancel);
        btnRow.appendChild(btnOk);
        box.appendChild(msg);
        box.appendChild(btnRow);
        overlay.appendChild(box);
        document.body.appendChild(overlay);

        const close = result => { overlay.remove(); resolve(result); };
        btnOk.addEventListener('click', () => close(true));
        btnCancel.addEventListener('click', () => close(false));
        overlay.addEventListener('click', e => { if (e.target === overlay) close(false); });
    });
}

function showToast(message, type = 'info') {
    const colors = { success: '#198754', danger: '#dc3545', info: '#0dcaf0', warning: '#ffc107' };
    const toast = document.createElement('div');
    toast.style.cssText = `
        position:fixed; bottom:1.5rem; right:1.5rem; z-index:9999;
        background:${colors[type] || '#333'}; color:white;
        padding:.65rem 1.1rem; border-radius:8px;
        font-size:.88rem; box-shadow:0 4px 16px rgba(0,0,0,.2);
        transition:opacity .3s; max-width:320px;
    `;
    if (type === 'warning') toast.style.color = '#212529';
    toast.textContent = message;
    document.body.appendChild(toast);
    setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2800);
}
