/**
 * Wild Dragon MAM - Premiere Pro Panel
 * Main JavaScript file for the CEP panel
 * Features: #30 FCP XML Export & Conform, #31 Hi-Res Auto-Relink, #32 GUI Redesign
 */

// ============================================================================
// State Management
// ============================================================================

const state = {
    serverUrl:        localStorage.getItem('mam_server_url') || 'http://localhost:7434',
    apiToken:         localStorage.getItem('mam_api_token') || '',
    isConnected:      false,
    isConnecting:     false,
    selectedAsset:    null,
    assets:           [],
    projects:         [],
    selectedProject:  'all',
    searchQuery:      '',
    currentPage:      0,
    pageSize:         50,
    totalAssets:      0,
    downloadProgress: 0,
    isDownloading:    false,
    thumbCache:       {},
    importedAssets:   JSON.parse(localStorage.getItem('mam_imported_assets') || '{}'),
    exportPanelVisible:  false,
    currentSequenceName: '',
    // Advanced features state
    conformPanelVisible: false,
    relinkPanelVisible:  false,
    selectedPreset: 'broadcast',
    timelineData:   null,
    relinkClips:    [],
    conformJobId:   null,
    conformPollTimer: null,
    // Tabs state
    currentTab: 'library',
    growingAssets: [],
    growingPollInterval: null,
};

// ============================================================================
// DOM Elements
// ============================================================================

let elements = {};

function initDOMElements() {
    elements = {
        serverUrlInput:    document.getElementById('server-url'),
        apiTokenInput:     document.getElementById('api-token'),
        connectBtn:        document.getElementById('connect-btn'),
        statusIndicator:   document.getElementById('status-indicator'),
        searchInput:       document.getElementById('search-input'),
        projectFilter:     document.getElementById('project-filter'),
        assetGrid:         document.getElementById('asset-grid'),
        emptyState:        document.getElementById('empty-state'),
        detailsPanel:      document.getElementById('details-panel'),
        detailsFilename:   document.getElementById('details-filename'),
        detailsCodec:      document.getElementById('details-codec'),
        detailsResolution: document.getElementById('details-resolution'),
        detailsFps:        document.getElementById('details-fps'),
        detailsDuration:   document.getElementById('details-duration'),
        detailsSize:       document.getElementById('details-size'),
        detailsTags:       document.getElementById('details-tags'),
        importBtn:         document.getElementById('import-btn'),
        importHiresBtn:    document.getElementById('import-hires-btn'),
        importAllBtn:      document.getElementById('import-all-btn'),
        mountLiveBtn:      document.getElementById('mount-live-btn'),
        relinkBtn:         document.getElementById('relink-btn'),
        exportTimelineBtn: document.getElementById('export-timeline-btn'),
        progressContainer: document.getElementById('progress-container'),
        progressLabel:     document.getElementById('progress-label'),
        progressFill:      document.getElementById('progress-fill'),
        exportPanel:       document.getElementById('export-panel'),
        exportSeqName:     document.getElementById('export-seq-name'),
        exportProjSelect:  document.getElementById('export-proj-select'),
        exportClipInfo:    document.getElementById('export-clip-info'),
        exportConfirmBtn:  document.getElementById('export-confirm-btn'),
        exportCancelBtn:   document.getElementById('export-cancel-btn'),
        seqInfoBar:        document.getElementById('seq-info-bar'),
        seqInfoName:       document.getElementById('seq-info-name'),
        seqRefreshBtn:     document.getElementById('seq-refresh-btn'),
        // Tabs
        tabLibrary:        document.getElementById('tab-library'),
        tabGrowing:        document.getElementById('tab-growing'),
        growingCount:      document.getElementById('growing-count'),
        libraryContainer:  document.getElementById('library-container'),
        growingContainer:  document.getElementById('growing-container'),
        growingGrid:       document.getElementById('growing-grid'),
        growingEmptyState: document.getElementById('growing-empty-state'),
        // Advanced: Conform panel
        exportConformBtn:      document.getElementById('export-conform-btn'),
        exportConformOverlay:  document.getElementById('export-conform-overlay'),
        exportConformPanel:    document.getElementById('export-conform-panel'),
        exportConformCloseBtn: document.getElementById('export-conform-close-btn'),
        exportConformCancelBtn:document.getElementById('export-conform-cancel-btn'),
        exportConformStartBtn: document.getElementById('export-conform-start-btn'),
        presetCards:           document.getElementById('preset-cards'),
        conformCodec:          document.getElementById('conform-codec'),
        conformQuality:        document.getElementById('conform-quality'),
        conformResolution:     document.getElementById('conform-resolution'),
        conformAudio:          document.getElementById('conform-audio'),
        conformClipInfo:       document.getElementById('conform-clip-info'),
        // Advanced: Relink panel
        fetchRelinkBtn:        document.getElementById('fetch-relink-btn'),
        relinkOverlay:         document.getElementById('relink-overlay'),
        relinkPanel:           document.getElementById('relink-panel'),
        relinkCloseBtn:        document.getElementById('relink-close-btn'),
        relinkCancelBtn:       document.getElementById('relink-cancel-btn'),
        relinkStartBtn:        document.getElementById('relink-start-btn'),
        clipList:              document.getElementById('clip-list'),
        relinkSummary:         document.getElementById('relink-summary'),
        relinkSummaryText:     document.getElementById('relink-summary-text'),
        relinkSummaryDetail:   document.getElementById('relink-summary-detail'),
        // Connection UI toggle
        connectionForm: document.getElementById('connection-form'),
        connectedBar:   document.getElementById('connected-bar'),
        disconnectBtn:  document.getElementById('disconnect-btn'),
        connectedHost:  document.getElementById('connected-host'),
    };
}

// ============================================================================
// Thumbnail Lazy Loading
// ============================================================================

const thumbObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            const img     = entry.target;
            const assetId = img.dataset.assetId;
            if (assetId && !img.dataset.loaded) loadThumbnail(img, assetId);
            thumbObserver.unobserve(img);
        }
    });
}, { rootMargin: '100px' });

async function loadThumbnail(img, assetId) {
    if (state.thumbCache[assetId]) {
        img.src = state.thumbCache[assetId];
        img.dataset.loaded = '1';
        return;
    }
    try {
        const r = await fetch(`${state.serverUrl}/api/v1/assets/${assetId}/thumbnail`, {
            headers: { Accept: 'application/json' },
        });
        if (!r.ok) return;
        const { url } = await r.json();
        if (!url) return;
        state.thumbCache[assetId] = url;
        img.src = url;
        img.dataset.loaded = '1';
    } catch (_) {}
}

// ============================================================================
// Initialization
// ============================================================================

document.addEventListener('DOMContentLoaded', () => {
    initDOMElements();
    setupEventListeners();
    restoreSettings();
    logMessage('Wild Dragon MAM panel initialized');
});

function setupEventListeners() {
    elements.serverUrlInput.addEventListener('input', (e) => {
        state.serverUrl = e.target.value.trim().replace(/\/$/, '');
        localStorage.setItem('mam_server_url', state.serverUrl);
        state.thumbCache = {};
    });
    elements.apiTokenInput.addEventListener('input', (e) => {
        state.apiToken = e.target.value.trim();
        localStorage.setItem('mam_api_token', state.apiToken);
    });
    elements.connectBtn.addEventListener('click', connectToServer);
    elements.disconnectBtn.addEventListener('click', disconnectFromServer);
    elements.searchInput.addEventListener('input', debounce(handleSearch, 300));
    elements.projectFilter.addEventListener('change', handleProjectFilter);
    elements.assetGrid.addEventListener('click', handleAssetClick);
    elements.importBtn.addEventListener('click', importSelectedAsset);
    elements.importHiresBtn.addEventListener('click', importSelectedAssetHires);
    elements.importAllBtn.addEventListener('click', importAllAssets);
    elements.mountLiveBtn.addEventListener('click', mountLiveAsset);
    elements.relinkBtn.addEventListener('click', relinkSelectedAsset);
    elements.exportTimelineBtn.addEventListener('click', startExportTimeline);
    elements.exportConfirmBtn.addEventListener('click', confirmExportTimeline);
    elements.exportCancelBtn.addEventListener('click', cancelExportTimeline);
    elements.seqRefreshBtn.addEventListener('click', refreshCurrentSequenceInfo);

    // Tabs
    elements.tabLibrary.addEventListener('click', () => switchTab('library'));
    elements.tabGrowing.addEventListener('click', () => switchTab('growing'));
    elements.growingGrid.addEventListener('click', handleAssetClick);

    // Advanced: Conform panel
    elements.exportConformBtn.addEventListener('click', showAdvancedExportPanel);
    elements.exportConformCloseBtn.addEventListener('click', hideAdvancedExportPanel);
    elements.exportConformCancelBtn.addEventListener('click', hideAdvancedExportPanel);
    elements.exportConformStartBtn.addEventListener('click', startConformFromPanel);
    elements.presetCards.addEventListener('click', handlePresetSelection);

    // Advanced: Relink panel
    elements.fetchRelinkBtn.addEventListener('click', fetchAndRelinkAll);
    elements.relinkCloseBtn.addEventListener('click', hideRelinkPanel);
    elements.relinkCancelBtn.addEventListener('click', hideRelinkPanel);
    elements.relinkStartBtn.addEventListener('click', startBatchRelink);
}

function restoreSettings() {
    state.serverUrl = state.serverUrl.replace(/\/+$/, '');
    localStorage.setItem('mam_server_url', state.serverUrl);
    elements.serverUrlInput.value = state.serverUrl;
    elements.apiTokenInput.value  = state.apiToken;
}

// ============================================================================
// Auth: inject Bearer token
// ============================================================================

const _originalFetch = window.fetch.bind(window);
window.fetch = function (input, init) {
    init = init || {};
    const url = typeof input === 'string' ? input : (input && input.url) || '';
    if (state.apiToken && state.serverUrl && url.startsWith(state.serverUrl)) {
        const headers = new Headers(init.headers || {});
        if (!headers.has('Authorization')) {
            headers.set('Authorization', 'Bearer ' + state.apiToken);
        }
        init.headers = headers;
    }
    return _originalFetch(input, init);
};

// ============================================================================
// Server Connection
// ============================================================================

async function connectToServer() {
    if (state.isConnecting) return;

    state.isConnecting = true;
    updateConnectionStatus('connecting');
    elements.connectBtn.disabled = true;

    try {
        const response = await fetch(`${state.serverUrl}/api/v1/projects`, {
            method: 'GET',
            headers: { Accept: 'application/json' },
            credentials: 'include',
        });

        if (response.ok) {
            state.isConnected = true;
            updateConnectionStatus('connected');
            try { elements.connectedHost.textContent = new URL(state.serverUrl).hostname; }
            catch (_) { elements.connectedHost.textContent = state.serverUrl; }
            elements.connectionForm.classList.add('hidden');
            elements.connectedBar.classList.remove('hidden');
            logMessage('Connected to Wild Dragon MAM');

            const projectData = await response.json();
            await fetchProjects(projectData);
            await fetchAssets();
            refreshCurrentSequenceInfo();
            startGrowingPoll();
        } else {
            throw new Error(`HTTP ${response.status}`);
        }
    } catch (error) {
        console.error('Connection error:', error);
        state.isConnected = false;
        updateConnectionStatus('disconnected');
        elements.connectBtn.textContent = 'Connect';
        showErrorMessage(`Failed to connect: ${error.message}`);
        stopGrowingPoll();
    } finally {
        state.isConnecting = false;
        elements.connectBtn.disabled = false;
    }
}

function updateConnectionStatus(status) {
    const indicator = elements.statusIndicator;
    indicator.classList.remove('connected', 'connecting');
    if (status === 'connected')       indicator.classList.add('connected');
    else if (status === 'connecting') indicator.classList.add('connecting');
}

function disconnectFromServer() {
    state.isConnected   = false;
    state.assets        = [];
    state.growingAssets = [];
    state.selectedAsset = null;
    stopGrowingPoll();
    updateConnectionStatus('disconnected');

    // Clear grids back to empty state
    elements.assetGrid.innerHTML   = '';
    elements.growingGrid.innerHTML = '';
    elements.assetGrid.appendChild(elements.emptyState);
    elements.growingGrid.appendChild(elements.growingEmptyState);

    // Hide secondary panels
    elements.detailsPanel.classList.add('hidden');
    elements.seqInfoBar.classList.add('hidden');

    // Disable action buttons
    [
        elements.importBtn, elements.importHiresBtn,
        elements.mountLiveBtn, elements.relinkBtn,
        elements.exportConformBtn, elements.fetchRelinkBtn,
    ].forEach(btn => { btn.disabled = true; });

    // Swap connection UI
    elements.connectedBar.classList.add('hidden');
    elements.connectionForm.classList.remove('hidden');
}

// ============================================================================
// API Calls
// ============================================================================

async function fetchProjects(preloadedData) {
    try {
        let projects;
        if (preloadedData) {
            projects = Array.isArray(preloadedData) ? preloadedData : [];
        } else {
            const response = await fetch(`${state.serverUrl}/api/v1/projects`, {
                headers: { Accept: 'application/json' },
                credentials: 'include',
            });
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            const data = await response.json();
            projects = Array.isArray(data) ? data : [];
        }

        state.projects = projects;

        const savedProject = state.selectedProject;
        elements.projectFilter.innerHTML = '<option value="all">All Projects</option>';
        state.projects.forEach((p) => {
            const opt = document.createElement('option');
            opt.value       = p.id;
            opt.textContent = p.name;
            elements.projectFilter.appendChild(opt);
        });
        elements.projectFilter.value = savedProject;

        populateExportProjectSelect();

        logMessage(`Loaded ${state.projects.length} projects`);
    } catch (error) {
        console.error('Error fetching projects:', error);
    }
}

async function fetchAssets(page = 0) {
    if (!state.isConnected) return;
    try {
        const params = new URLSearchParams({
            offset: page * state.pageSize,
            limit:  state.pageSize,
        });
        if (state.searchQuery)               params.append('search',     state.searchQuery);
        if (state.selectedProject !== 'all') params.append('project_id', state.selectedProject);

        const response = await fetch(
            `${state.serverUrl}/api/v1/assets?${params.toString()}`,
            { headers: { Accept: 'application/json' }, credentials: 'include' }
        );
        if (!response.ok) throw new Error(`HTTP ${response.status}`);

        const data        = await response.json();
        state.assets      = data.assets || [];
        state.totalAssets = data.total  || 0;
        state.currentPage = page;

        renderAssets();
        logMessage(`Loaded ${state.assets.length} assets`);
    } catch (error) {
        console.error('Error fetching assets:', error);
        showErrorMessage('Failed to fetch assets');
    }
}

async function fetchAssetDetails(assetId) {
    if (!state.isConnected) return null;
    try {
        const response = await fetch(`${state.serverUrl}/api/v1/assets/${assetId}`, {
            headers: { Accept: 'application/json' },
            credentials: 'include',
        });
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        return await response.json();
    } catch (error) {
        console.error('Error fetching asset details:', error);
        return null;
    }
}

async function getSignedDownloadUrl(assetId) {
    const response = await fetch(`${state.serverUrl}/api/v1/assets/${assetId}/stream`, {
        headers: { Accept: 'application/json' },
        credentials: 'include',
    });
    if (!response.ok) throw new Error(`HTTP ${response.status} from /stream`);
    const { url } = await response.json();
    if (!url) throw new Error('Stream endpoint returned no URL');
    return url.startsWith('/') ? state.serverUrl + url : url;
}

async function getHiresDownloadInfo(assetId) {
    const response = await fetch(`${state.serverUrl}/api/v1/assets/${assetId}/hires`, {
        headers: { Accept: 'application/json' },
        credentials: 'include',
    });
    if (!response.ok) throw new Error(`HTTP ${response.status} from /hires`);
    const data = await response.json();
    if (!data.url) throw new Error('Hi-res endpoint returned no URL');
    return data;
}

// ============================================================================
// UI Rendering
// ============================================================================

function renderAssets() {
    elements.assetGrid.innerHTML = '';

    if (state.assets.length === 0) {
        elements.emptyState.style.display = 'flex';
        return;
    }

    elements.emptyState.style.display = 'none';
    state.assets.forEach((asset) => {
        elements.assetGrid.appendChild(createAssetCard(asset));
    });
}

function createAssetCard(asset) {
    const card = document.createElement('div');
    card.className = 'asset-card';
    if (state.selectedAsset && state.selectedAsset.id === asset.id) {
        card.classList.add('selected');
    }
    card.dataset.assetId = asset.id;

    const thumbnail = document.createElement('div');
    thumbnail.className = 'asset-thumbnail';

    const img = document.createElement('img');
    img.dataset.assetId = asset.id;
    img.alt             = escapeHtml(asset.display_name || asset.filename || '');
    img.style.cssText   = 'width:100%;height:100%;object-fit:cover;display:block;';
    img.onerror         = () => { img.style.display = 'none'; };
    thumbnail.appendChild(img);
    thumbObserver.observe(img);
    card.appendChild(thumbnail);

    const info = document.createElement('div');
    info.className = 'asset-info';

    const name       = asset.display_name || asset.filename || 'Untitled';
    const filenameEl = document.createElement('div');
    filenameEl.className   = 'asset-filename';
    filenameEl.title       = name;
    filenameEl.textContent = name;
    info.appendChild(filenameEl);

    let durationSec = asset.duration_ms ? asset.duration_ms / 1000 : null;
    if (asset.status === 'live' && asset.created_at) {
        durationSec = Math.floor((Date.now() - Date.parse(asset.created_at)) / 1000);
    }
    const codec = asset.codec || asset.media_type || 'video';
    const meta  = document.createElement('div');
    meta.className   = 'asset-meta';
    meta.innerHTML   = [
        '<span>' + (durationSec ? formatDuration(durationSec) : 'LIVE') + '</span>',
        '<span>' + escapeHtml(codec.toUpperCase()) + '</span>',
    ].join('');
    info.appendChild(meta);

    const statusStr   = asset.status || 'ready';
    const statusBadge = document.createElement('div');
    statusBadge.className   = 'asset-status-badge status-badge ' + statusStr;
    statusBadge.textContent = statusStr.toUpperCase();
    info.appendChild(statusBadge);

    card.appendChild(info);
    return card;
}

function showAssetDetails(asset) {
    state.selectedAsset = asset;
    elements.detailsPanel.classList.remove('hidden');

    elements.detailsFilename.textContent   = asset.display_name || asset.filename;
    elements.detailsCodec.textContent      = asset.codec || asset.media_type || 'Unknown';
    elements.detailsResolution.textContent = asset.resolution || 'N/A';
    elements.detailsFps.textContent        = asset.fps ? asset.fps + ' fps' : 'N/A';
    elements.detailsDuration.textContent   = asset.duration_ms
        ? formatDuration(asset.duration_ms / 1000)
        : 'N/A';
    elements.detailsSize.textContent       = asset.file_size
        ? formatFileSize(asset.file_size)
        : 'N/A';

    elements.detailsTags.innerHTML = '';
    const tags = asset.tags || [];
    if (tags.length > 0) {
        tags.forEach((tag) => {
            const el       = document.createElement('span');
            el.className   = 'tag';
            el.textContent = tag;
            elements.detailsTags.appendChild(el);
        });
    } else {
        elements.detailsTags.innerHTML =
            '<span style="color:var(--text-tertiary)">No tags</span>';
    }

    var isLive = asset.status === 'live';
    elements.importBtn.disabled      = isLive;
    elements.importHiresBtn.disabled = isLive;
    elements.mountLiveBtn.disabled   = !isLive;
    elements.relinkBtn.disabled = !(asset.status === 'ready' && state.importedAssets['live:' + asset.id]);
}

function hideAssetDetails() {
    state.selectedAsset = null;
    elements.detailsPanel.classList.add('hidden');
    elements.importBtn.disabled      = true;
    elements.importHiresBtn.disabled = true;
    elements.mountLiveBtn.disabled   = true;
    elements.relinkBtn.disabled      = true;
}

// ============================================================================
// Tabs and Growing Assets Polling
// ============================================================================

function switchTab(tabName) {
    if (!state.isConnected) return;

    state.currentTab = tabName;

    elements.tabLibrary.classList.toggle('active', tabName === 'library');
    elements.tabGrowing.classList.toggle('active', tabName === 'growing');

    elements.libraryContainer.classList.toggle('hidden', tabName !== 'library');
    elements.growingContainer.classList.toggle('hidden', tabName !== 'growing');

    hideAssetDetails();

    if (tabName === 'library') {
        fetchAssets();
    } else {
        pollGrowingAssets();
    }
}

function startGrowingPoll() {
    stopGrowingPoll();
    pollGrowingAssets();
    state.growingPollInterval = setInterval(pollGrowingAssets, 5000);
}

function stopGrowingPoll() {
    if (state.growingPollInterval) {
        clearInterval(state.growingPollInterval);
        state.growingPollInterval = null;
    }
}

async function pollGrowingAssets() {
    if (!state.isConnected) return;
    try {
        const params = new URLSearchParams({
            limit: 100
        });
        if (state.selectedProject !== 'all') {
            params.append('project_id', state.selectedProject);
        }
        if (state.searchQuery) {
            params.append('search', state.searchQuery);
        }

        const response = await fetch(`${state.serverUrl}/api/v1/assets?${params.toString()}`, {
            headers: { Accept: 'application/json' },
            credentials: 'include'
        });
        if (!response.ok) return;

        const data = await response.json();
        const allAssets = data.assets || [];

        state.growingAssets = allAssets.filter(a =>
            a.status === 'live' ||
            a.status === 'ingesting' ||
            a.status === 'processing' ||
            (a.status === 'ready' && state.importedAssets['live:' + a.id])
        );

        const count = state.growingAssets.filter(a => a.status === 'live' || a.status === 'ingesting' || a.status === 'processing').length;
        elements.growingCount.textContent = count;
        elements.growingCount.style.display = count > 0 ? 'inline-block' : 'none';

        if (state.currentTab === 'growing') {
            renderGrowingAssets();

            if (state.selectedAsset) {
                const updatedSelected = state.growingAssets.find(a => a.id === state.selectedAsset.id);
                if (updatedSelected) {
                    showAssetDetails(updatedSelected);
                } else {
                    const latest = await fetchAssetDetails(state.selectedAsset.id);
                    if (latest) {
                        showAssetDetails(latest);
                    }
                }
            }
        }
    } catch (error) {
        console.error('Error polling growing assets:', error);
    }
}

function renderGrowingAssets() {
    elements.growingGrid.innerHTML = '';

    if (state.growingAssets.length === 0) {
        elements.growingEmptyState.style.display = 'flex';
        return;
    }

    elements.growingEmptyState.style.display = 'none';
    state.growingAssets.forEach((asset) => {
        elements.growingGrid.appendChild(createAssetCard(asset));
    });
}

// ============================================================================
// Mount Live
// ============================================================================

async function mountLiveAsset() {
    if (!state.selectedAsset) {
        showErrorMessage('No asset selected');
        return;
    }
    var asset = state.selectedAsset;
    try {
        elements.mountLiveBtn.disabled = true;
        showProgress('Resolving SMB path…', 10);

        var res = await fetch(state.serverUrl + '/api/v1/assets/' + asset.id + '/live-path', {
            headers: { Accept: 'application/json' },
            credentials: 'include',
        });
        if (!res.ok) {
            var body = await res.json().catch(function () { return {}; });
            throw new Error(body.error || ('HTTP ' + res.status));
        }
        var info = await res.json();

        var isMac = navigator.platform.indexOf('Mac') !== -1;
        var hostPath = isMac ? info.posix_path : info.win_path;

        showProgress('Importing live file…', 60);
        await importFileToPremiereProject(hostPath);

        state.importedAssets['live:' + asset.id] = {
            assetId:     asset.id,
            displayName: info.display_name,
            livePath:    hostPath,
        };
        try { localStorage.setItem('mam_imported_assets', JSON.stringify(state.importedAssets)); } catch (_) {}

        startLiveStatusPoll(asset.id);
        hideProgress();
        showSuccessMessage('Mounted live: ' + info.display_name);
    } catch (err) {
        hideProgress();
        showErrorMessage('Mount live failed: ' + err.message);
    } finally {
        elements.mountLiveBtn.disabled = !(state.selectedAsset && state.selectedAsset.status === 'live');
    }
}

var _livePolls = {};
function startLiveStatusPoll(assetId) {
    if (_livePolls[assetId]) return;
    _livePolls[assetId] = setInterval(async function () {
        try {
            var r = await fetch(state.serverUrl + '/api/v1/assets/' + assetId, {
                headers: { Accept: 'application/json' },
                credentials: 'include',
            });
            if (!r.ok) return;
            var a = await r.json();
            if (a.status === 'ready') {
                clearInterval(_livePolls[assetId]);
                delete _livePolls[assetId];
                logMessage('Live asset ' + assetId + ' finalized — relink available');
                if (state.selectedAsset && state.selectedAsset.id === assetId) {
                    state.selectedAsset = a;
                    showAssetDetails(a);
                }
                _showFlash('"' + (a.display_name || assetId) + '" finalized — click Relink to swap to hi-res', 'info-message');
            }
        } catch (_) {}
    }, 5000);
}

async function relinkSelectedAsset() {
    if (!state.selectedAsset) return;
    var asset = state.selectedAsset;
    var entry = state.importedAssets['live:' + asset.id];
    if (!entry) {
        showErrorMessage('No live mount recorded for this asset');
        return;
    }

    try {
        elements.relinkBtn.disabled = true;
        showProgress('Fetching hi-res link…', 10);

        var hires = await getHiresDownloadInfo(asset.id);
        var safeName = sanitizeFilename(hires.filename || (asset.display_name || asset.id) + '.mov');

        showProgress('Downloading hi-res' + (hires.file_size ? ' (' + formatFileSize(hires.file_size) + ')' : '') + '…', 20);
        var localPath = await downloadFile(hires.url, safeName);

        showProgress('Relinking in Premiere…', 85);
        await relinkInPremiere(entry.livePath, localPath);

        saveImportMapping(localPath, safeName, asset);
        hideProgress();
        showSuccessMessage('Relinked to hi-res: ' + safeName);
    } catch (err) {
        hideProgress();
        showErrorMessage('Relink failed: ' + err.message);
    } finally {
        elements.relinkBtn.disabled = false;
    }
}

function relinkInPremiere(oldPath, newPath) {
    var oldEsc = oldPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var newEsc = newPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var script = [
        '(function () {',
        '  var out = { success: false, relinked: 0, message: "" };',
        '  try {',
        '    if (!app.project) { out.message = "No project open"; return JSON.stringify(out); }',
        '    var oldPath = "' + oldEsc + '";',
        '    var newPath = "' + newEsc + '";',
        '    function walk(item) {',
        '      for (var i = 0; i < item.children.numItems; i++) {',
        '        var c = item.children[i];',
        '        if (c.type === 1 || c.type === 2) {',
        '          if (c.getMediaPath() === oldPath) {',
        '            c.changeMediaPath(newPath);',
        '            out.relinked++;',
        '          }',
        '        }',
        '        if (c.children && c.children.numItems > 0) walk(c);',
        '      }',
        '    }',
        '    walk(app.project.rootItem);',
        '    out.success = out.relinked > 0;',
        '    out.message = out.relinked + " clip(s) relinked";',
        '  } catch (e) { out.message = e.message; }',
        '  return JSON.stringify(out);',
        '})();',
    ].join('\n');

    return new Promise(function (resolve, reject) {
        csInterface.evalScript(script, function (resultStr) {
            try {
                var parsed = JSON.parse(resultStr);
                if (parsed.success) resolve(parsed);
                else reject(new Error(parsed.message || 'relink found no matching clips'));
            } catch (e) {
                reject(new Error('ExtendScript error: ' + resultStr));
            }
        });
    });
}

// ============================================================================
// Search and Filter
// ============================================================================

function handleSearch(e) {
    state.searchQuery = e.target.value;
    state.currentPage = 0;
    if (state.currentTab === 'library') {
        fetchAssets();
    } else {
        pollGrowingAssets();
    }
}

function handleProjectFilter(e) {
    state.selectedProject = e.target.value;
    state.currentPage     = 0;
    if (state.currentTab === 'library') {
        fetchAssets();
    } else {
        pollGrowingAssets();
    }
}

// ============================================================================
// Import — Proxy
// ============================================================================

async function importSelectedAsset() {
    if (!state.selectedAsset) {
        showErrorMessage('No asset selected');
        return;
    }
    await importAsset(state.selectedAsset);
}

async function importAllAssets() {
    if (state.assets.length === 0) {
        showErrorMessage('No assets to import');
        return;
    }
    for (const asset of state.assets) {
        await importAsset(asset);
    }
    showSuccessMessage('All assets imported');
}

async function importAsset(asset) {
    try {
        elements.importBtn.disabled = true;

        showProgress('Getting download link...', 5);
        const url = await getSignedDownloadUrl(asset.id);

        const safeName = sanitizeFilename(
            (asset.display_name || asset.filename || asset.id) + '.mp4'
        );
        showProgress('Downloading ' + safeName + '...', 10);
        const filePath = await downloadFile(url, safeName);

        showProgress('Importing into Premiere Pro...', 85);
        await importFileToPremiereProject(filePath);

        saveImportMapping(filePath, safeName, asset);

        hideProgress();
        showSuccessMessage('Imported: ' + safeName);
    } catch (error) {
        console.error('Import error:', error);
        hideProgress();
        showErrorMessage('Import failed: ' + error.message);
    } finally {
        elements.importBtn.disabled = !state.selectedAsset;
    }
}

// ============================================================================
// Import — Hi-Res Original
// ============================================================================

async function importSelectedAssetHires() {
    if (!state.selectedAsset) {
        showErrorMessage('No asset selected');
        return;
    }
    await importAssetHires(state.selectedAsset);
}

async function importAssetHires(asset) {
    try {
        elements.importHiresBtn.disabled = true;

        showProgress('Getting hi-res link...', 5);
        let hiresInfo;
        try {
            hiresInfo = await getHiresDownloadInfo(asset.id);
        } catch (err) {
            throw new Error('No hi-res source: ' + err.message);
        }

        const sizeNote = hiresInfo.file_size ? ' (' + formatFileSize(hiresInfo.file_size) + ')' : '';
        const safeName = sanitizeFilename(
            hiresInfo.filename || (asset.display_name || asset.id) + '.mxf'
        );

        showProgress('Downloading hi-res' + sizeNote + '...', 10);
        const filePath = await downloadFile(hiresInfo.url, safeName);

        showProgress('Importing hi-res into Premiere Pro...', 85);
        await importFileToPremiereProject(filePath);

        saveImportMapping(filePath, safeName, asset);

        hideProgress();
        showSuccessMessage('Hi-res imported: ' + safeName);
    } catch (error) {
        console.error('Hi-res import error:', error);
        hideProgress();
        showErrorMessage('Hi-res import failed: ' + error.message);
    } finally {
        elements.importHiresBtn.disabled = !state.selectedAsset;
    }
}

function saveImportMapping(filePath, safeName, asset) {
    const entry = {
        assetId:     asset.id,
        displayName: asset.display_name || asset.filename || '',
    };
    state.importedAssets[filePath]          = entry;
    state.importedAssets['name:' + safeName] = entry;
    try {
        localStorage.setItem('mam_imported_assets', JSON.stringify(state.importedAssets));
    } catch (_) {}
}

// ============================================================================
// Active Sequence Info Bar
// ============================================================================

function refreshCurrentSequenceInfo() {
    csInterface.evalScript('getActiveSequence()', function (resultStr) {
        try {
            var parsed = JSON.parse(resultStr);
            state.currentSequenceName = parsed.sequenceName || '';
            if (state.currentSequenceName) {
                elements.seqInfoName.textContent = state.currentSequenceName;
                elements.seqInfoBar.classList.remove('hidden');
            } else {
                elements.seqInfoBar.classList.add('hidden');
            }
        } catch (e) {
            elements.seqInfoBar.classList.add('hidden');
        }
    });
}

// ============================================================================
// Export Panel UI (Push Timeline to MAM)
// ============================================================================

function populateExportProjectSelect() {
    elements.exportProjSelect.innerHTML = '<option value="">— Select project —</option>';
    state.projects.forEach(function (p) {
        var opt = document.createElement('option');
        opt.value       = p.id;
        opt.textContent = p.name;
        if (p.id === state.selectedProject) opt.selected = true;
        elements.exportProjSelect.appendChild(opt);
    });
}

function showExportPanel(timelineData) {
    elements.exportSeqName.value = timelineData.sequenceName || 'Sequence 1';
    populateExportProjectSelect();

    var totalClips  = (timelineData.clips || []).length;
    var matchedClips = resolveClipsToAssets(timelineData.clips || []).filter(function (c) {
        return c.asset_id;
    }).length;

    elements.exportClipInfo.textContent =
        matchedClips + ' of ' + totalClips + ' clip(s) matched to MAM assets';
    elements.exportClipInfo.style.color =
        matchedClips === 0 ? 'var(--status-red)' : 'var(--text-secondary)';

    elements.exportPanel.dataset.timelineJson = JSON.stringify(timelineData);
    elements.exportPanel.classList.remove('hidden');
    state.exportPanelVisible = true;
    elements.exportSeqName.focus();
}

function hideExportPanel() {
    elements.exportPanel.classList.add('hidden');
    state.exportPanelVisible = false;
}

// ============================================================================
// Timeline Export — Premiere → MAM
// ============================================================================

async function startExportTimeline() {
    if (!state.isConnected) {
        showErrorMessage('Connect to MAM first');
        return;
    }

    if (state.exportPanelVisible) {
        hideExportPanel();
        return;
    }

    showProgress('Reading Premiere timeline...', 20);

    const timelineData = await new Promise(function (resolve) {
        csInterface.evalScript('exportTimelineData()', function (resultStr) {
            try { resolve(JSON.parse(resultStr)); }
            catch (e) { resolve({ success: false, message: 'Parse error: ' + e.message, clips: [] }); }
        });
    });

    hideProgress();

    if (!timelineData.success) {
        showErrorMessage('Timeline read failed: ' + (timelineData.message || 'unknown error'));
        return;
    }

    if (!timelineData.clips || timelineData.clips.length === 0) {
        showErrorMessage('No clips found in the active sequence');
        return;
    }

    showExportPanel(timelineData);
}

async function confirmExportTimeline() {
    var timelineData;
    try {
        timelineData = JSON.parse(elements.exportPanel.dataset.timelineJson || '{}');
    } catch (e) {
        showErrorMessage('Invalid timeline data — try reading again');
        return;
    }

    var seqName   = (elements.exportSeqName.value || '').trim() || 'Sequence 1';
    var projectId = elements.exportProjSelect.value;
    if (!projectId) {
        showErrorMessage('Select a target project');
        return;
    }

    var resolved = resolveClipsToAssets(timelineData.clips || []);
    var matched  = resolved.filter(function (c) { return c.asset_id; });

    if (matched.length === 0) {
        hideExportPanel();
        showErrorMessage('No clips matched MAM assets — import proxies or hi-res first');
        return;
    }

    hideExportPanel();
    showProgress('Creating sequence in MAM...', 20);

    try {
        var seqId = await upsertSequence(
            projectId,
            seqName,
            timelineData.frameRate || 59.94,
            timelineData.width     || 1920,
            timelineData.height    || 1080
        );

        showProgress('Writing ' + matched.length + ' clip(s)...', 60);

        var clipPayload = matched.map(function (c) {
            return {
                asset_id:            c.asset_id,
                track:               c.trackIndex,
                timeline_in_frames:  c.timelineInFrames,
                timeline_out_frames: c.timelineOutFrames,
                source_in_frames:    c.sourceInFrames,
                source_out_frames:   c.sourceOutFrames,
            };
        });

        var clipsRes = await fetch(
            state.serverUrl + '/api/v1/sequences/' + seqId + '/clips',
            {
                method:      'PUT',
                headers:     { 'Content-Type': 'application/json', Accept: 'application/json' },
                credentials: 'include',
                body:        JSON.stringify(clipPayload),
            }
        );
        if (!clipsRes.ok) throw new Error('Clip push failed: HTTP ' + clipsRes.status);

        hideProgress();
        showSuccessMessage('Timeline pushed: ' + matched.length + ' clip(s) → "' + seqName + '"');

        var skipped = resolved.length - matched.length;
        if (skipped > 0) {
            _showFlash(skipped + ' clip(s) skipped — not in MAM (import them first)', 'info-message');
        }
    } catch (err) {
        hideProgress();
        showErrorMessage('Export failed: ' + err.message);
    }
}

function cancelExportTimeline() {
    hideExportPanel();
}

function resolveClipsToAssets(clips) {
    return clips.map(function (clip) {
        var entry = state.importedAssets[clip.filePath];
        if (!entry) entry = state.importedAssets['name:' + clip.fileName];
        return Object.assign({}, clip, { asset_id: entry ? entry.assetId : null });
    });
}

async function upsertSequence(projectId, name, frameRate, width, height) {
    var listRes = await fetch(
        state.serverUrl + '/api/v1/sequences?project_id=' + encodeURIComponent(projectId),
        { headers: { Accept: 'application/json' }, credentials: 'include' }
    );
    if (listRes.ok) {
        var seqs     = await listRes.json();
        var existing = seqs.find(function (s) { return s.name === name; });
        if (existing) {
            await fetch(state.serverUrl + '/api/v1/sequences/' + existing.id, {
                method:      'PUT',
                headers:     { 'Content-Type': 'application/json', Accept: 'application/json' },
                credentials: 'include',
                body:        JSON.stringify({ frame_rate: frameRate, width: width, height: height }),
            });
            return existing.id;
        }
    }

    var createRes = await fetch(state.serverUrl + '/api/v1/sequences', {
        method:      'POST',
        headers:     { 'Content-Type': 'application/json', Accept: 'application/json' },
        credentials: 'include',
        body:        JSON.stringify({
            project_id: projectId,
            name:       name,
            frame_rate: frameRate,
            width:      width,
            height:     height,
        }),
    });
    if (!createRes.ok) throw new Error('Failed to create sequence: HTTP ' + createRes.status);
    var seq = await createRes.json();
    return seq.id;
}

// ============================================================================
// #30 — FCP XML Export & Conform
// ============================================================================

async function showAdvancedExportPanel() {
    if (!state.isConnected) {
        showErrorMessage('Connect to MAM first');
        return;
    }

    if (state.conformPanelVisible) {
        hideAdvancedExportPanel();
        return;
    }

    showProgress('Reading Premiere timeline...', 20);

    const timelineData = await new Promise(function (resolve) {
        csInterface.evalScript('exportTimelineData()', function (resultStr) {
            try { resolve(JSON.parse(resultStr)); }
            catch (e) { resolve({ success: false, message: 'Parse error: ' + e.message, clips: [] }); }
        });
    });

    hideProgress();

    if (!timelineData.success) {
        showErrorMessage('Timeline read failed: ' + (timelineData.message || 'unknown error'));
        return;
    }

    if (!timelineData.clips || timelineData.clips.length === 0) {
        showErrorMessage('No clips found in the active sequence');
        return;
    }

    state.timelineData = timelineData;

    // Update clip info in panel
    var totalClips  = timelineData.clips.length;
    var matchedClips = resolveClipsToAssets(timelineData.clips).filter(function (c) {
        return c.asset_id;
    }).length;

    elements.conformClipInfo.textContent = matchedClips + ' of ' + totalClips + ' clip(s) matched';

    // Enable start button only if some clips are matched
    elements.exportConformStartBtn.disabled = matchedClips === 0;

    // Apply preset defaults
    applyPreset(state.selectedPreset);

    openSlidePanel(
        elements.exportConformOverlay,
        elements.exportConformPanel
    );
    state.conformPanelVisible = true;
}

function hideAdvancedExportPanel() {
    closeSlidePanel(
        elements.exportConformOverlay,
        elements.exportConformPanel
    );
    state.conformPanelVisible = false;

    if (state.conformPollTimer) {
        clearInterval(state.conformPollTimer);
        state.conformPollTimer = null;
    }
}

function handlePresetSelection(e) {
    var card = e.target.closest('.preset-card');
    if (!card) return;

    document.querySelectorAll('.preset-card').forEach(function (el) {
        el.classList.remove('selected');
    });
    card.classList.add('selected');

    var preset = card.dataset.preset;
    state.selectedPreset = preset;
    applyPreset(preset);
}

function applyPreset(preset) {
    switch (preset) {
        case 'broadcast':
            elements.conformCodec.value      = 'prores_hq';
            elements.conformQuality.value    = 'high';
            elements.conformResolution.value = '1080p';
            elements.conformAudio.value      = 'broadcast';
            break;
        case 'web':
            elements.conformCodec.value      = 'h264';
            elements.conformQuality.value    = 'medium';
            elements.conformResolution.value = '1080p';
            elements.conformAudio.value      = 'web';
            break;
        case 'archive':
            elements.conformCodec.value      = 'prores_4444';
            elements.conformQuality.value    = 'high';
            elements.conformResolution.value = 'uhd';
            elements.conformAudio.value      = 'archive';
            break;
        case 'custom':
            // Leave current selections as-is
            break;
    }
}

async function startConformFromPanel() {
    if (!state.timelineData) {
        showErrorMessage('No timeline data — re-open the panel');
        return;
    }

    elements.exportConformStartBtn.disabled = true;
    showProgress('Generating FCP XML...', 10);

    try {
        var fcpXml = generateFcpXml(state.timelineData);

        var codec      = elements.conformCodec.value;
        var quality    = elements.conformQuality.value;
        var resolution = elements.conformResolution.value;
        var audio      = elements.conformAudio.value;

        showProgress('Starting conform job...', 30);
        var job = await startConformJob(fcpXml, codec, quality, resolution, audio);

        hideAdvancedExportPanel();
        showProgress('Conform job started — polling...', 40);

        state.conformJobId = job.jobId;
        pollConformProgress(job.jobId);
    } catch (err) {
        hideProgress();
        showErrorMessage('Conform failed: ' + err.message);
        elements.exportConformStartBtn.disabled = false;
    }
}

function generateFcpXml(timelineData) {
    var seqName   = escapeXml(timelineData.sequenceName || 'Sequence 1');
    var frameRate = timelineData.frameRate || 29.97;
    var width     = timelineData.width     || 1920;
    var height    = timelineData.height    || 1080;
    var clips     = timelineData.clips     || [];

    // Calculate total duration from clips
    var totalFrames = 0;
    clips.forEach(function (c) {
        var end = c.timelineOutFrames || 0;
        if (end > totalFrames) totalFrames = end;
    });
    if (totalFrames < 1) totalFrames = 100;

    var duration = timecodeFromFrames(totalFrames, frameRate);
    var frameRateStr = formatFrameRate(frameRate);

    var xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
    xml += '<!DOCTYPE fcpxml>\n';
    xml += '<fcpxml version="1.10">\n';
    xml += '  <resources>\n';

    // Build a resource for each unique source
    var seen = {};
    var resourceId = 1;
    clips.forEach(function (clip) {
        var key = clip.filePath || clip.fileName || 'clip_' + resourceId;
        if (!seen[key]) {
            seen[key] = true;
            var name = escapeXml(clip.fileName || 'Clip ' + resourceId);
            var path = escapeXml(clip.filePath || '');
            var srcDur = timecodeFromFrames(
                (clip.sourceOutFrames || 100) - (clip.sourceInFrames || 0),
                frameRate
            );
            xml += '    <asset id="r' + resourceId + '" name="' + name + '" src="' + path + '" duration="' + srcDur + '" start="' + timecodeFromFrames(0, frameRate) + '" format="r0"/>\n';
            resourceId++;
        }
    });

    xml += '    <format id="r0" frameDuration="' + frameRateStr + '" width="' + width + '" height="' + height + '"/>\n';
    xml += '  </resources>\n';
    xml += '  <library>\n';
    xml += '    <event name="Conform Export">\n';
    xml += '      <project name="' + seqName + '" duration="' + duration + '">\n';
    xml += '        <sequence duration="' + duration + '" format="r0">\n';
    xml += '          <spine>\n';

    // Resolve clip paths to asset IDs and build track layout
    var resolvedClips = resolveClipsToAssets(clips);
    var trackGroups = {};
    resolvedClips.forEach(function (clip) {
        var track = clip.trackIndex !== undefined ? clip.trackIndex : 0;
        if (!trackGroups[track]) trackGroups[track] = [];
        trackGroups[track].push(clip);
    });

    var trackKeys = Object.keys(trackGroups).sort();
    if (trackKeys.length === 0) trackKeys = ['0'];

    trackKeys.forEach(function (trackKey) {
        var trackClips = trackGroups[trackKey];
        trackClips.forEach(function (clip) {
            var name = escapeXml(clip.fileName || 'Clip');
            var tcStart = timecodeFromFrames(clip.timelineInFrames || 0, frameRate);
            var dur = timecodeFromFrames(
                (clip.timelineOutFrames || clip.sourceOutFrames || 100) - (clip.timelineInFrames || 0),
                frameRate
            );
            var srcStart = timecodeFromFrames(clip.sourceInFrames || 0, frameRate);

            // Find the resource ID
            var rid = 1;
            var seen2 = {};
            var count = 0;
            resolvedClips.forEach(function (c) {
                var key = c.filePath || c.fileName || 'clip_';
                if (!seen2[key]) {
                    seen2[key] = true;
                    count++;
                    if (key === (clip.filePath || clip.fileName || '')) {
                        rid = count;
                    }
                }
            });

            xml += '            <clip name="' + name + '" offset="' + tcStart + '" duration="' + dur + '" start="' + srcStart + '" ref="r' + rid + '"/>\n';
        });
    });

    xml += '          </spine>\n';
    xml += '        </sequence>\n';
    xml += '      </project>\n';
    xml += '    </event>\n';
    xml += '  </library>\n';
    xml += '</fcpxml>\n';

    return xml;
}

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

function formatFrameRate(fps) {
    // FCP XML uses rational time scale: e.g. 100/2997 for 29.97fps
    var tolerance = 0.001;
    if (Math.abs(fps - 23.976) < tolerance || Math.abs(fps - 23.98) < tolerance) return '1001/24000';
    if (Math.abs(fps - 24) < tolerance) return '1/24';
    if (Math.abs(fps - 25) < tolerance) return '1/25';
    if (Math.abs(fps - 29.97) < tolerance) return '1001/30000';
    if (Math.abs(fps - 30) < tolerance) return '1/30';
    if (Math.abs(fps - 50) < tolerance) return '1/50';
    if (Math.abs(fps - 59.94) < tolerance) return '1001/60000';
    if (Math.abs(fps - 60) < tolerance) return '1/60';
    return '1001/30000'; // default to 29.97
}

function timecodeFromFrames(frames, fps) {
    if (fps <= 0) fps = 29.97;
    var totalSeconds = frames / fps;
    var h = Math.floor(totalSeconds / 3600);
    var m = Math.floor((totalSeconds % 3600) / 60);
    var s = Math.floor(totalSeconds % 60);
    var f = Math.round((totalSeconds - Math.floor(totalSeconds)) * fps);
    return pad2(h) + ':' + pad2(m) + ':' + pad2(s) + ':' + pad2(f);
}

function pad2(num) {
    return (num < 10 ? '0' : '') + num;
}

async function startConformJob(fcpXml, codec, quality, resolution, audio) {
    // Normalize codec value for the worker
    var workerCodec = codec;
    if (codec === 'prores_hq' || codec === 'prores_4444') workerCodec = 'prores';

    // Normalize resolution value for the worker
    var workerResolution = resolution;
    if (resolution === 'uhd') workerResolution = '3840x2160';
    else if (resolution === '1080p') workerResolution = '1920x1080';
    else if (resolution === '720p') workerResolution = '1280x720';

    // Create or find the sequence in MAM (same flow as export timeline)
    var timelineData = state.timelineData;
    var seqName   = (timelineData && timelineData.sequenceName) || 'Conformed Sequence';
    var frameRate = (timelineData && timelineData.frameRate)    || 29.97;
    var width     = (timelineData && timelineData.width)        || 1920;
    var height    = (timelineData && timelineData.height)       || 1080;

    var seqId = await upsertSequence(
        state.selectedProject,
        seqName,
        frameRate,
        width,
        height
    );

    var response = await fetch(state.serverUrl + '/api/v1/sequences/' + seqId + '/conform', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        credentials: 'include',
        body: JSON.stringify({
            fcp_xml:   fcpXml,
            codec:     workerCodec,
            quality:   quality,
            resolution: workerResolution,
            audio:     audio,
        }),
    });

    if (!response.ok) {
        var errBody = {};
        try { errBody = await response.json(); } catch (_) {}
        throw new Error(errBody.error || ('HTTP ' + response.status));
    }

    return await response.json();
}

function pollConformProgress(jobId) {
    // jobId is in format "conform:123" — matches GET /api/v1/jobs/:id
    var pollInterval = setInterval(async function () {
        try {
            var response = await fetch(
                state.serverUrl + '/api/v1/jobs/' + encodeURIComponent(jobId),
                { headers: { Accept: 'application/json' }, credentials: 'include' }
            );

            if (!response.ok) {
                clearInterval(pollInterval);
                hideProgress();
                showErrorMessage('Conform job status check failed');
                return;
            }

            var status = await response.json();
            var apiStatus = status.status || 'waiting';

            if (status.progress !== undefined) {
                showProgress('Conforming... ' + status.progress + '%', status.progress);
            }

            if (apiStatus === 'completed') {
                clearInterval(pollInterval);
                hideProgress();
                showSuccessMessage('Conform complete! Asset ready in MAM.');
                state.conformPollTimer = null;
                // Refresh assets to show the new conformed asset
                fetchAssets();
            } else if (apiStatus === 'failed') {
                clearInterval(pollInterval);
                hideProgress();
                showErrorMessage('Conform failed: ' + (status.error || 'Unknown error'));
                state.conformPollTimer = null;
            }
        } catch (err) {
            // Transient error — keep polling
        }
    }, 2000);

    state.conformPollTimer = pollInterval;
}

// ============================================================================
// #31 — Hi-Res Auto-Relink
// ============================================================================

async function fetchAndRelinkAll() {
    if (!state.isConnected) {
        showErrorMessage('Connect to MAM first');
        return;
    }

    if (state.relinkPanelVisible) {
        hideRelinkPanel();
        return;
    }

    showProgress('Reading Premiere timeline...', 20);

    const timelineData = await new Promise(function (resolve) {
        // Use the enhanced function that includes clipInstanceId
        csInterface.evalScript('exportTimelineDataWithIds()', function (resultStr) {
            try { resolve(JSON.parse(resultStr)); }
            catch (e) { resolve({ success: false, message: 'Parse error: ' + e.message, clips: [] }); }
        });
    });

    hideProgress();

    if (!timelineData.success) {
        showErrorMessage('Timeline read failed: ' + (timelineData.message || 'unknown error'));
        return;
    }

    if (!timelineData.clips || timelineData.clips.length === 0) {
        showErrorMessage('No clips found in the active sequence');
        return;
    }

    // Resolve clips to MAM assets
    var resolved = resolveClipsToAssets(timelineData.clips);
    var matched  = resolved.filter(function (c) { return c.asset_id; });

    if (matched.length === 0) {
        showErrorMessage('No timeline clips matched MAM assets — import them first');
        return;
    }

    state.timelineData = timelineData;
    state.relinkClips  = resolved;

    showClipSelection(resolved);
}

function showClipSelection(clips) {
    elements.clipList.innerHTML = '';

    clips.forEach(function (clip, index) {
        var item = document.createElement('div');
        item.className = 'clip-list-item';
        item.dataset.index = index;

        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.className = 'clip-list-item-checkbox';
        checkbox.checked = !!clip.asset_id;
        checkbox.disabled = !clip.asset_id;

        var info = document.createElement('div');
        info.className = 'clip-list-item-info';

        var name = document.createElement('div');
        name.className = 'clip-list-item-name';
        name.textContent = clip.fileName || 'Unknown clip';

        var meta = document.createElement('div');
        meta.className = 'clip-list-item-meta';
        meta.textContent = clip.asset_id
            ? 'Track ' + (clip.trackIndex + 1) + ' | MAM asset: ' + clip.asset_id
            : 'Track ' + (clip.trackIndex + 1) + ' | Not matched';

        info.appendChild(name);
        info.appendChild(meta);

        var status = document.createElement('div');
        status.className = 'clip-list-item-status ' + (clip.asset_id ? 'matched' : 'unmatched');
        status.textContent = clip.asset_id ? 'Matched' : 'Unmatched';

        item.appendChild(checkbox);
        item.appendChild(info);
        item.appendChild(status);

        elements.clipList.appendChild(item);
    });

    elements.relinkSummary.classList.add('hidden');
    elements.relinkStartBtn.disabled = false;

    openSlidePanel(
        elements.relinkOverlay,
        elements.relinkPanel
    );
    state.relinkPanelVisible = true;
}

function hideRelinkPanel() {
    closeSlidePanel(
        elements.relinkOverlay,
        elements.relinkPanel
    );
    state.relinkPanelVisible = false;
    state.relinkClips = [];
}

async function startBatchRelink() {
    var checkboxes = elements.clipList.querySelectorAll('.clip-list-item-checkbox');
    var selectedClips = [];

    checkboxes.forEach(function (cb, index) {
        if (cb.checked && !cb.disabled) {
            var clip = state.relinkClips[index];
            if (clip && clip.asset_id) {
                selectedClips.push(clip);
            }
        }
    });

    if (selectedClips.length === 0) {
        showErrorMessage('No clips selected for relink');
        return;
    }

    elements.relinkStartBtn.disabled = true;
    showProgress('Requesting batch trim for ' + selectedClips.length + ' clip(s)...', 10);

    try {
        var segments = await requestBatchTrim(selectedClips);
        showProgress('Downloading ' + segments.length + ' segment(s)...', 30);
        var results = await downloadAndRelink(segments);
        hideProgress();
        showRelinkSummary(results);
    } catch (err) {
        hideProgress();
        showErrorMessage('Batch relink failed: ' + err.message);
        elements.relinkStartBtn.disabled = false;
    }
}

async function requestBatchTrim(clips) {
    var payload = clips.map(function (clip) {
        return {
            assetId:           clip.asset_id,
            filename:          clip.fileName || 'clip.mxf',
            trackIndex:        clip.trackIndex || 0,
            sourceInFrames:    clip.sourceInFrames || 0,
            sourceOutFrames:   clip.sourceOutFrames || 0,
            timelineInFrames:  clip.timelineInFrames || 0,
            timelineOutFrames: clip.timelineOutFrames || 0,
        };
    });

    var response = await fetch(state.serverUrl + '/api/v1/assets/batch-trim', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ clips: payload }),
    });

    if (!response.ok) {
        var errBody = {};
        try { errBody = await response.json(); } catch (_) {}
        throw new Error(errBody.error || ('HTTP ' + response.status));
    }

    var data = await response.json();
    return data.clips || [];
}

async function downloadAndRelink(segments) {
    var results = [];

    for (var i = 0; i < segments.length; i++) {
        var seg = segments[i];
        try {
            showProgress(
                'Getting signed URL for segment ' + (i + 1) + ' of ' + segments.length + '...',
                25 + ((i / segments.length) * 50)
            );

            // Fetch signed URL for the temp segment
            var urlRes = await fetch(
                state.serverUrl + '/api/v1/assets/temp-segment-url/' + encodeURIComponent(seg.clipInstanceId),
                { headers: { Accept: 'application/json' }, credentials: 'include' }
            );
            if (!urlRes.ok) throw new Error('Segment not ready: HTTP ' + urlRes.status);
            var urlData = await urlRes.json();

            var safeName = sanitizeFilename('segment_' + seg.clipInstanceId + '.mov');
            var localPath = await downloadFile(urlData.url, safeName);

            showProgress('Relinking segment ' + (i + 1) + ' in Premiere...', 85);
            var relinkResult = await relinkClipToNewMedia(seg.clipInstanceId, localPath);

            results.push({
                clipName:   seg.clipInstanceId || safeName,
                success:    true,
                localPath:  localPath,
                message:    relinkResult.message || 'Relinked',
            });
        } catch (err) {
            results.push({
                clipName:   seg.clipInstanceId || 'Unknown',
                success:    false,
                localPath:  null,
                message:    err.message,
            });
        }
    }

    return results;
}

function relinkClipToNewMedia(clipId, filePath) {
    var safePath = filePath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
    var script = [
        '(function () {',
        '  var out = { success: false, message: "" };',
        '  try {',
        '    if (!app.project) { out.message = "No project open"; return JSON.stringify(out); }',
        '    var newPath = "' + safePath + '";',
        '    function walk(item) {',
        '      for (var i = 0; i < item.children.numItems; i++) {',
        '        var c = item.children[i];',
        '        if (c.type === 1 || c.type === 2) {',
        '          try {',
        '            c.changeMediaPath(newPath);',
        '            out.success = true;',
        '            out.message = "Relinked: " + c.name;',
        '            return;',
        '          } catch (e) {}',
        '        }',
        '        if (c.children && c.children.numItems > 0) walk(c);',
        '      }',
        '    }',
        '    walk(app.project.rootItem);',
        '    if (!out.success) out.message = "No matching clip found for relink";',
        '  } catch (e) { out.message = e.message; }',
        '  return JSON.stringify(out);',
        '})();',
    ].join('\n');

    return new Promise(function (resolve, reject) {
        csInterface.evalScript(script, function (resultStr) {
            try {
                var parsed = JSON.parse(resultStr);
                resolve(parsed);
            } catch (e) {
                reject(new Error('ExtendScript error: ' + resultStr));
            }
        });
    });
}

function showRelinkSummary(results) {
    var succeeded = results.filter(function (r) { return r.success; }).length;
    var failed    = results.filter(function (r) { return !r.success; }).length;

    elements.relinkSummary.classList.remove('hidden');
    elements.relinkSummaryText.textContent = succeeded + ' of ' + results.length + ' clip(s) relinked to hi-res';

    var detailParts = [];
    if (succeeded > 0) detailParts.push(succeeded + ' succeeded');
    if (failed > 0) detailParts.push(failed + ' failed');
    elements.relinkSummaryDetail.textContent = detailParts.join(', ') || 'No clips processed';

    elements.relinkStartBtn.disabled = false;

    _showFlash(results.length + ' clip(s) processed — ' + succeeded + ' relinked, ' + failed + ' failed', 'info-message');
}

// ============================================================================
// #32 — Slide Panel Management
// ============================================================================

function openSlidePanel(overlay, panel) {
    overlay.classList.add('open');
    panel.classList.add('open');
}

function closeSlidePanel(overlay, panel) {
    overlay.classList.remove('open');
    panel.classList.remove('open');
}

// ============================================================================
// UI Helpers
// ============================================================================

function handleAssetClick(e) {
    var card = e.target.closest('.asset-card');
    if (!card) return;

    document.querySelectorAll('.asset-card.selected').forEach(function (el) {
        el.classList.remove('selected');
    });
    card.classList.add('selected');

    var assetId = card.dataset.assetId;
    var asset   = state.assets.find(function (a) { return a.id === assetId; }) ||
                  state.growingAssets.find(function (a) { return a.id === assetId; });
    if (asset) showAssetDetails(asset);
}

function showProgress(label, percent) {
    elements.progressContainer.classList.add('visible');
    elements.progressLabel.textContent = label;
    state.downloadProgress = percent;
    updateProgressUI();
}

function hideProgress() {
    elements.progressContainer.classList.remove('visible');
    state.downloadProgress = 0;
    updateProgressUI();
}

function updateProgressUI() {
    elements.progressFill.style.width = state.downloadProgress + '%';
}

function showErrorMessage(message) {
    _showFlash(message, 'error-message');
}

function showSuccessMessage(message) {
    _showFlash(message, 'success-message');
}

function _showFlash(message, className) {
    var el         = document.createElement('div');
    el.className   = className;
    el.textContent = message;
    var anchor     = document.querySelector('.search-filter-area');
    anchor.insertBefore(el, anchor.firstChild);
    setTimeout(function () { el.remove(); }, 5000);
}

function logMessage(message) {
    console.log('[MAM Panel] ' + message);
}

// ============================================================================
// File Download and Premiere Import
// ============================================================================

function downloadFile(url, filename) {
    return new Promise(function (resolve, reject) {
        try {
            var https = require('https');
            var http  = require('http');
            var fs    = require('fs');
            var path  = require('path');
            var os    = require('os');

            var tempPath = path.join(os.tmpdir(), filename);
            var file     = fs.createWriteStream(tempPath);
            var protocol = url.startsWith('https') ? https : http;

            // Native http.get bypasses window.fetch, so the Bearer
            // interceptor never fires. Inject Authorization manually for
            // same-server URLs (proxy /video etc.); off-host presigned URLs
            // don't need it.
            var reqOpts = url;
            if (state.apiToken && state.serverUrl && url.startsWith(state.serverUrl)) {
              try {
                var p = new URL(url);
                reqOpts = {
                  hostname: p.hostname,
                  port:     p.port || (p.protocol === 'https:' ? 443 : 80),
                  path:     p.pathname + (p.search || ''),
                  headers:  { 'Authorization': 'Bearer ' + state.apiToken },
                };
              } catch (_) { /* fall back to url string */ }
            }

            var req = protocol.get(reqOpts, function (res) {
                if (res.statusCode !== 200) {
                    file.close();
                    fs.unlink(tempPath, function () {});
                    reject(new Error('Download HTTP ' + res.statusCode));
                    return;
                }

                var total    = parseInt(res.headers['content-length'] || '0', 10);
                var received = 0;

                res.on('data', function (chunk) {
                    received += chunk.length;
                    if (total > 0) {
                        state.downloadProgress = 10 + (received / total) * 75;
                        updateProgressUI();
                    }
                });

                res.pipe(file);

                file.on('finish', function () {
                    file.close(function () { resolve(tempPath); });
                });

                file.on('error', function (err) {
                    fs.unlink(tempPath, function () {});
                    reject(err);
                });
            });

            // Without a timeout an unreachable host or a CEP-Node TLS hiccup
            // makes this Promise sit forever. 15s covers slow LTE connect +
            // handshake; once bytes flow there's no cap.
            req.setTimeout(15000, function () {
              var host = (typeof reqOpts === 'string') ? reqOpts : reqOpts.hostname;
              req.destroy(new Error('Download timed out after 15s (no response from ' + host + ')'));
            });

            req.on('error', function (err) {
                fs.unlink(tempPath, function () {});
                reject(err);
            });
        } catch (err) {
            reject(new Error('Node.js unavailable for download: ' + err.message));
        }
    });
}

function importFileToPremiereProject(filePath) {
    return new Promise(function (resolve, reject) {
        var safePath = filePath.replace(/\\/g, '\\\\');

        // Premiere can deadlock on importFiles() if a hidden prompt appears
        // (off-screen modal, etc.). The evalScript callback then never fires.
        // Race the import against a 60s timeout so the user sees an error
        // instead of a frozen spinner.
        var done  = false;
        var timer = setTimeout(function () {
            if (done) return;
            done = true;
            reject(new Error('ExtendScript importFiles() did not return within 60s — Premiere may have a hidden dialog'));
        }, 60000);

        var script = [
            '(function() {',
            '  var result = { success: false, message: "" };',
            '  try {',
            '    var lf = new File("C:/Users/Administrator/Documents/df-import-log.txt");',
            '    var opened = lf.open("a");',
            '    result.message = "opened=" + opened + " fsName=" + lf.fsName + " err=" + (lf.error || "none");',
            '    if (opened) { lf.writeln((new Date()).toString() + " v1.2.5 noop probe"); lf.close(); }',
            '    /* keep success=false so the message surfaces in the panel popup */',
            '  } catch (e) { result.message = "caught: " + e.message; }',
            '  return JSON.stringify(result);',
            '})();',
        ].join('\n');

        csInterface.evalScript(script, function (resultStr) {
            if (done) return;
            done = true;
            clearTimeout(timer);
            try {
                var parsed = JSON.parse(resultStr);
                if (parsed.success) resolve(parsed);
                else reject(new Error(parsed.message));
            } catch (e) {
                reject(new Error('ExtendScript error: ' + resultStr));
            }
        });
    });
}

// ============================================================================
// Utility Functions
// ============================================================================

function debounce(func, delay) {
    var timeout;
    return function () {
        var args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function () { func.apply(null, args); }, delay);
    };
}

function escapeHtml(str) {
    return String(str)
        .replace(/&/g,  '&amp;')
        .replace(/</g,  '&lt;')
        .replace(/>/g,  '&gt;')
        .replace(/"/g,  '&quot;')
        .replace(/'/g,  '&#39;');
}

function sanitizeFilename(name) {
    return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
}

function formatDuration(seconds) {
    if (!seconds) return 'N/A';
    var h = Math.floor(seconds / 3600);
    var m = Math.floor((seconds % 3600) / 60);
    var s = Math.floor(seconds % 60);
    if (h > 0) return h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
    return m + ':' + String(s).padStart(2, '0');
}

function formatFileSize(bytes) {
    if (!bytes) return '0 B';
    var k     = 1024;
    var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    var i     = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
