/**
 * The Studio Player - Core Muxer Script
 * 
 * Handles the UI, File Loading, and Muxing Logic.
 * Slot limits are dynamically determined by DOM elements.
 */

jQuery(document).ready(function($) {
    const $container = $('.thespl-muxer-app');
    if (!$container.length) return;

    // Helper to scope selectors
    const getEl = (id) => $container.find('#' + id)[0];
    const $get = (id) => $container.find('#' + id);

    const formatBytes = (bytes, decimals = 2) => {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const dm = decimals < 0 ? 0 : decimals;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    };

	const validateSubdirValue = (val) => {
		const invalidChars = /[^a-zA-Z0-9_\-\/]/g;
		const hasDoubleDots = /\.\./.test(val);
		if (!val) {
			return true;
		}
		if (invalidChars.test(val) || hasDoubleDots) {
			return false;
		}
		return true;
	};

	const getUploadSubdir = () => {
		const $input = $get('upload-subdir');
		if ($input && $input.length) {
			const v = $input.val();
			if (v && typeof v === 'string' && v.trim() !== '') {
				return v.trim();
			}
		}
		if (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars.upload_subdir) {
			return String(thespl_muxer_vars.upload_subdir);
		}
		return '';
	};

	let thesplChunkSizeLogged = false;

	const initChunkSizeControl = () => {
		const jq = $('#upload-chunk-size');
		const note = $('#upload-chunk-size-note');
		if (!jq.length || typeof thespl_muxer_vars === 'undefined') {
			return;
		}
		const presets = [
			{ value: 262144, label: '256 KB (Slow, very safe)' },
			{ value: 524288, label: '512 KB (Stable)' },
			{ value: 1048576, label: '1 MB (Standard)' },
			{ value: 2097152, label: '2 MB (Faster)' },
			{ value: 5242880, label: '5 MB (Fastest)' }
		];
		const maxUpload = typeof thespl_muxer_vars.max_upload_size === 'number' ? thespl_muxer_vars.max_upload_size : parseInt(thespl_muxer_vars.max_upload_size || '0', 10);
		const effective = [];
		for (let i = 0; i < presets.length; i++) {
			if (maxUpload > 0 && presets[i].value > maxUpload) {
				continue;
			}
			effective.push(presets[i]);
		}
		const options = effective.length ? effective : [presets[0]];
		jq.empty();
		for (let i = 0; i < options.length; i++) {
			const opt = document.createElement('option');
			opt.value = String(options[i].value);
			opt.textContent = options[i].label;
			jq.append(opt);
		}
		// Always default to the largest preset that still fits into the server upload limit.
		let current = options[options.length - 1].value;
		jq.val(String(current));
		if (note.length) {
			const limitLabel = typeof thesplMuxerI18n !== 'undefined' && thesplMuxerI18n.chunkSizeServerLimit ? thesplMuxerI18n.chunkSizeServerLimit : '';
			const baseInfo = typeof thesplMuxerI18n !== 'undefined' && thesplMuxerI18n.chunkSizeInfo ? thesplMuxerI18n.chunkSizeInfo : 'Smaller chunks are safer for unstable connections. Larger chunks are faster.';
			if (limitLabel) {
				note.text(baseInfo + ' ' + limitLabel);
			} else {
				note.text(baseInfo);
			}
		}
	};

	initChunkSizeControl();

	const getChunkSize = () => {
		const jq = $('#upload-chunk-size');
		if (jq.length) {
			const v = parseInt(jq.val(), 10);
			if (Number.isFinite(v) && v > 0) {
				if (!thesplChunkSizeLogged && typeof console !== 'undefined' && console && typeof console.info === 'function') {
					console.info('[THESPL Muxer] Chunk size (DOM #upload-chunk-size):', v, {
						source: 'dom',
						settingValue: (typeof thespl_muxer_vars !== 'undefined' ? thespl_muxer_vars.upload_chunk_size : null),
						maxUploadSize: (typeof thespl_muxer_vars !== 'undefined' ? thespl_muxer_vars.max_upload_size : null)
					});
					thesplChunkSizeLogged = true;
				}
				return v;
			}
		}
		if (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars.upload_chunk_size) {
			const v = parseInt(thespl_muxer_vars.upload_chunk_size, 10);
			const size = Number.isFinite(v) && v > 0 ? v : 1048576;
			if (!thesplChunkSizeLogged && typeof console !== 'undefined' && console && typeof console.info === 'function') {
				console.info('[THESPL Muxer] Chunk size (Settings thespl_muxer_vars.upload_chunk_size):', size, {
					source: 'settings',
					rawValue: v,
					maxUploadSize: thespl_muxer_vars.max_upload_size
				});
				thesplChunkSizeLogged = true;
			}
			return size;
		}
		if (!thesplChunkSizeLogged && typeof console !== 'undefined' && console && typeof console.info === 'function') {
			console.info('[THESPL Muxer] Chunk size (default):', 1048576, {
				source: 'default'
			});
			thesplChunkSizeLogged = true;
		}
		return 1048576;
	};

    if (typeof window.THESPL_Muxer_Engine === 'undefined') {
        const localizedEngine = (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars && thespl_muxer_vars.engine) ? thespl_muxer_vars.engine : {};
        const baseMaxSlots = Number.isInteger(localizedEngine.max_slots) ? localizedEngine.max_slots : 2;
        const baseMaxChannels = Number.isInteger(localizedEngine.max_channels) ? localizedEngine.max_channels : Math.max(baseMaxSlots * 2, 4);
        const baseRates = Array.isArray(localizedEngine.allowed_sample_rates) && localizedEngine.allowed_sample_rates.length ? localizedEngine.allowed_sample_rates : [44100];
        const baseBits = Array.isArray(localizedEngine.allowed_bit_depths) && localizedEngine.allowed_bit_depths.length ? localizedEngine.allowed_bit_depths : [16];

        window.THESPL_Muxer_Engine = {
            max_slots: baseMaxSlots,
            max_channels: baseMaxChannels,
            allowed_sample_rates: baseRates,
            allowed_bit_depths: baseBits,

            validateFormat: function(sampleRate, bitDepth) {
                const maxSampleRate = Math.max.apply(null, this.allowed_sample_rates);
                const maxBitDepth = Math.max.apply(null, this.allowed_bit_depths);
                const srOk = sampleRate <= maxSampleRate;
                const bdOk = bitDepth <= maxBitDepth;

                if (!srOk && !bdOk) return `${maxSampleRate / 1000}kHz & ${maxBitDepth}-bit maximum`;
                if (!srOk) return `${maxSampleRate / 1000}kHz maximum`;
                if (!bdOk) return `${maxBitDepth}-bit maximum`;

                return true;
            },

            assignToChannels: function(channelData, pcm, fileIndex, maxFrames) {
                const chBase = fileIndex * 2;

                if (chBase + 1 >= channelData.length) return;

                const L = channelData[chBase];
                const R = channelData[chBase + 1];
                const frames = pcm.length / 2;

                for (let j = 0; j < frames; j++) {
                    if (j < maxFrames) {
                        L[j] = pcm[j*2];
                        R[j] = pcm[j*2+1];
                    }
                }
            },

            calculateTotalChannels: function(activeFileCount) {
                return Math.min(activeFileCount * 2, this.max_channels);
            }
        };
    }



    const Engine = window.THESPL_Muxer_Engine;

    const DOM_SLOTS = $container.find('.file-slot input[type="file"]').length;
    const ENGINE_MAX_SLOTS = typeof Engine.max_slots === 'number' ? Engine.max_slots : DOM_SLOTS;
    const MAX_SLOTS = Math.min(DOM_SLOTS, ENGINE_MAX_SLOTS);

    const ENGINE_ALLOWED_RATES = Array.isArray(Engine.allowed_sample_rates) && Engine.allowed_sample_rates.length ? Engine.allowed_sample_rates : [44100];
    const ENGINE_ALLOWED_BITS  = Array.isArray(Engine.allowed_bit_depths) && Engine.allowed_bit_depths.length ? Engine.allowed_bit_depths : [16];

    const MAX_SAMPLE_RATE = Math.max.apply(null, ENGINE_ALLOWED_RATES);
    const MAX_BIT_DEPTH   = Math.max.apply(null, ENGINE_ALLOWED_BITS);
    const ALLOW_HIGH_RES  = (MAX_SAMPLE_RATE > 44100) || (MAX_BIT_DEPTH > 16);
    const HAS_PRO_WAVEFORM_GENERATION = !!(Engine.computeWaveform && Engine.createWaveformJson);
    
    const ENABLE_MEMORY_OPTIMIZATION = true;

    const isSlotLocked = (slotIndex) => {
        const $slot = $container.find('.file-slot[data-slot="' + slotIndex + '"]');
        return $slot.hasClass('pro-locked');
    };

    const getCapabilitySummary = () => {
        const variants = MAX_SLOTS;
        const srText = ALLOW_HIGH_RES ? '48 kHz' : '44.1 kHz';
        const bdText = ALLOW_HIGH_RES ? '24-bit' : '16-bit';
        return `${variants} variants, ${srText} / ${bdText}`;
    };

    const getEditionStatusText = () => {
        if (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars && thespl_muxer_vars.has_pro_assets) {
            return 'Pro access active';
        }

        return 'Free';
    };

    const muxerI18n = window.thesplMuxerI18n || {};

    const formatI18n = (template, values = []) => {
        let autoIndex = 0;

        return String(template || '').replace(/%((\d+)\$)?([sd])/g, (match, _full, numberedIndex) => {
            let valueIndex = autoIndex;

            if (numberedIndex) {
                valueIndex = parseInt(numberedIndex, 10) - 1;
            } else {
                autoIndex += 1;
            }

            return typeof values[valueIndex] !== 'undefined' ? String(values[valueIndex]) : '';
        });
    };

    const getI18nText = (key, fallback, values = []) => {
        const template = Object.prototype.hasOwnProperty.call(muxerI18n, key) ? muxerI18n[key] : fallback;

        return formatI18n(template, values);
    };

    const escapeHtml = (value) => String(value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');

    const formatSampleRateLabel = (sampleRate) => {
        if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
            return '0 kHz';
        }

        return `${(sampleRate / 1000).toFixed(sampleRate % 1000 === 0 ? 0 : 1)} kHz`;
    };

    const formatBitDepthLabel = (bitDepth) => {
        if (!Number.isFinite(bitDepth) || bitDepth <= 0) {
            return '0-bit';
        }

        return `${bitDepth}-bit`;
    };

    let waveformAllowed = false;
    let waveformReason = '';

    const updateWaveformOutputBox = () => {
        const $box = $get('thespl-waveform-output-box');
        if (!$box || !$box.length) {
            return;
        }
        const $note = $get('thespl-waveform-output-text');
        if (!$note || !$note.length) {
            return;
        }
        const defaultColor = '#8b949e';
        const proColor = '#3fb950';
        const t = window.thesplMuxerI18n || {};
        if (HAS_PRO_WAVEFORM_GENERATION) {
            const proTxt = t.wfJsonProActive || 'Waveform JSON will be generated for this export.';
            $box.show();
            $note.text(proTxt).css('color', proColor);
            return;
        }
        $box.show();
        $note.text(t.wfJsonProOnly || 'Waveform JSON generation is available in Pro. Existing waveform files are displayed automatically in the player.').css('color', defaultColor);
    };

    const evaluateWaveformPolicy = () => {
        waveformAllowed = HAS_PRO_WAVEFORM_GENERATION;
        waveformReason = waveformAllowed ? 'will be generated.' : 'available in Pro.';
    };

    const hasDuplicateFile = (slotIndex) => {
        const file = fileSlots[slotIndex];

        if (!file) {
            return false;
        }

        return fileSlots.some((candidate, candidateIndex) => candidateIndex !== slotIndex && candidate && candidate.name === file.name && candidate.size === file.size);
    };

    const buildUnsupportedFormatMessage = (sampleRate, bitDepth) => {
        if (!ALLOW_HIGH_RES) {
            return getI18nText(
                'slotUnsupportedFormatFree',
                'Unsupported format: %1$s / %2$s stereo required.',
                [formatSampleRateLabel(MAX_SAMPLE_RATE), formatBitDepthLabel(MAX_BIT_DEPTH)]
            );
        }

        return getI18nText(
            'slotUnsupportedFormatPro',
            'Unsupported format: max %1$s / %2$s.',
            [formatSampleRateLabel(MAX_SAMPLE_RATE), formatBitDepthLabel(MAX_BIT_DEPTH)]
        );
    };

    const getBaseSlotState = (slotIndex) => {
        const file = fileSlots[slotIndex];
        const channels = channelCounts[slotIndex];
        const sampleRate = sampleRates[slotIndex];
        const bitDepth = bitDepths[slotIndex];

        if (!file) {
            return {
                hasFile: false,
                status: 'empty'
            };
        }

        if (channels === null) {
            return {
                hasFile: true,
                file,
                status: 'loading'
            };
        }

        if (channels === -1) {
            return {
                hasFile: true,
                file,
                status: 'blocking',
                message: getI18nText('slotUnsupportedWav', 'Unsupported format.')
            };
        }

        if (channels !== 2) {
            return {
                hasFile: true,
                file,
                status: 'blocking',
                message: getI18nText('slotUnsupportedChannels', 'Unsupported format: stereo WAV required.', [channels]),
                channelIssue: true
            };
        }

        const formatValid = Engine.validateFormat(sampleRate, bitDepth);

        if (formatValid !== true) {
            return {
                hasFile: true,
                file,
                status: 'blocking',
                message: buildUnsupportedFormatMessage(sampleRate, bitDepth),
                formatIssue: true,
                sampleRateIssue: sampleRate > MAX_SAMPLE_RATE,
                bitDepthIssue: bitDepth > MAX_BIT_DEPTH,
                sampleRate,
                bitDepth
            };
        }

        return {
            hasFile: true,
            file,
            status: 'ready',
            channels,
            sampleRate,
            bitDepth
        };
    };

    const getNegotiatedOutputProfile = (baseSlotStates) => {
        const compatibleStates = baseSlotStates.filter((slotState) => slotState.status === 'ready');

        if (!compatibleStates.length) {
            return null;
        }

        const sampleRatesList = compatibleStates.map((slotState) => slotState.sampleRate);
        const bitDepthList = compatibleStates.map((slotState) => slotState.bitDepth);
        const targetSampleRate = Math.min.apply(null, sampleRatesList);
        const targetBitDepth = Math.min.apply(null, bitDepthList);

        return {
            sampleRate: targetSampleRate,
            bitDepth: targetBitDepth,
            maxSampleRate: Math.max.apply(null, sampleRatesList),
            maxBitDepth: Math.max.apply(null, bitDepthList),
            hasSampleRateReduction: Math.max.apply(null, sampleRatesList) > targetSampleRate,
            hasBitDepthReduction: Math.max.apply(null, bitDepthList) > targetBitDepth
        };
    };

    const getSlotState = (slotIndex, baseSlotState, negotiatedProfile) => {
        if (!baseSlotState.hasFile) {
            return {
                hasFile: false,
                status: 'empty',
                isDuplicate: false,
                hasOutputReduction: false
            };
        }

        if ('loading' === baseSlotState.status) {
            return {
                hasFile: true,
                file: baseSlotState.file,
                status: 'loading',
                badgeClass: 'loading',
                slotClasses: ['loading'],
                message: getI18nText('analyzingFile', 'Analyzing...'),
                isDuplicate: false,
                hasOutputReduction: false
            };
        }

        if ('blocking' === baseSlotState.status) {
            return {
                hasFile: true,
                file: baseSlotState.file,
                status: 'blocking',
                badgeClass: 'error',
                slotClasses: ['error'],
                message: baseSlotState.message,
                isDuplicate: false,
                hasOutputReduction: false,
                sampleRateIssue: !!baseSlotState.sampleRateIssue,
                bitDepthIssue: !!baseSlotState.bitDepthIssue,
                channelIssue: !!baseSlotState.channelIssue,
                formatIssue: !!baseSlotState.formatIssue
            };
        }

        const isDuplicate = hasDuplicateFile(slotIndex);
        const warningMessages = [];
        let hasOutputReduction = false;

        if (negotiatedProfile && ALLOW_HIGH_RES && (baseSlotState.sampleRate > negotiatedProfile.sampleRate || baseSlotState.bitDepth > negotiatedProfile.bitDepth)) {
            hasOutputReduction = true;
            warningMessages.push(
                getI18nText(
                    'slotWarningReduced',
                    'Reduced to %1$s / %2$s.',
                    [formatSampleRateLabel(negotiatedProfile.sampleRate), formatBitDepthLabel(negotiatedProfile.bitDepth)]
                )
            );
        }

        if (isDuplicate) {
            warningMessages.push(getI18nText('slotDuplicate', 'Duplicate file.'));
        }

        const detailText = `${baseSlotState.channels}ch, ${formatSampleRateLabel(baseSlotState.sampleRate)}, ${formatBitDepthLabel(baseSlotState.bitDepth)} - ${(baseSlotState.file.size / 1024 / 1024).toFixed(2)} MB`;

        return {
            hasFile: true,
            file: baseSlotState.file,
            status: 'ready',
            badgeClass: warningMessages.length ? 'warning' : '',
            slotClasses: warningMessages.length ? ['warning'] : ['has-file'],
            detailText,
            message: warningMessages.length ? `${detailText} - ${warningMessages.join(' - ')}` : detailText,
            isDuplicate,
            hasOutputReduction,
            sampleRate: baseSlotState.sampleRate,
            bitDepth: baseSlotState.bitDepth
        };
    };

    const getMuxerAnalysis = () => {
        const baseSlotStates = [];
        let loadedCount = 0;
        let pendingCount = 0;
        let blockingCount = 0;
        let compatibleCount = 0;
        let hasSampleRateBlocker = false;
        let hasBitDepthBlocker = false;

        for (let i = 0; i < MAX_SLOTS; i++) {
            const baseSlotState = getBaseSlotState(i);
            baseSlotStates.push(baseSlotState);

            if (baseSlotState.hasFile) {
                loadedCount += 1;
            }
            if ('loading' === baseSlotState.status) {
                pendingCount += 1;
            }
            if ('blocking' === baseSlotState.status) {
                blockingCount += 1;
                hasSampleRateBlocker = hasSampleRateBlocker || !!baseSlotState.sampleRateIssue;
                hasBitDepthBlocker = hasBitDepthBlocker || !!baseSlotState.bitDepthIssue;
            }
            if ('ready' === baseSlotState.status) {
                compatibleCount += 1;
            }
        }

        const negotiatedProfile = getNegotiatedOutputProfile(baseSlotStates);
        const slotStates = baseSlotStates.map((baseSlotState, slotIndex) => getSlotState(slotIndex, baseSlotState, negotiatedProfile));
        const blockingState = slotStates.find((slotState) => 'blocking' === slotState.status) || null;
        const hasDuplicates = slotStates.some((slotState) => slotState.isDuplicate);
        const hasOutputReduction = slotStates.some((slotState) => slotState.hasOutputReduction);

        return {
            slotStates,
            loadedCount,
            pendingCount,
            blockingCount,
            compatibleCount,
            negotiatedProfile,
            blockingState,
            hasDuplicates,
            hasOutputReduction,
            hasSampleRateReduction: !!(negotiatedProfile && negotiatedProfile.hasSampleRateReduction),
            hasBitDepthReduction: !!(negotiatedProfile && negotiatedProfile.hasBitDepthReduction),
            hasSampleRateBlocker,
            hasBitDepthBlocker,
            canRun: loadedCount > 0 && pendingCount === 0 && blockingCount === 0 && compatibleCount === loadedCount
        };
    };
    
    let fileSlots = new Array(MAX_SLOTS).fill(null);
    let blobUrls = new Array(MAX_SLOTS).fill(null);
    let channelCounts = new Array(MAX_SLOTS).fill(null);
    let sampleRates = new Array(MAX_SLOTS).fill(null);
    let bitDepths = new Array(MAX_SLOTS).fill(null);
    let slotDurationsSeconds = new Array(MAX_SLOTS).fill(null);
    
    let isConverting = false;
    let isCancelled = false;
    let currentXHR = null; // Track active XHR for cancellation
    let libflacReady = false;
    let isSizeBlocked = false; // New flag for size limit check
    let generatedBlobs = [];
    let unloadGuard = false;
    var beforeUnloadHandler = function(e){
        if(!unloadGuard){return;}
        e.preventDefault();
        e.returnValue = 'Muxer / Upload in progress';
        return 'Muxer / Upload in progress';
    };
    const setUnloadGuard = function(active){
        unloadGuard = !!active;
        if(unloadGuard){window.addEventListener('beforeunload', beforeUnloadHandler);} else {window.removeEventListener('beforeunload', beforeUnloadHandler);} 
    };
    
    // Logging
    let logBuf = [];
    let logHtml = [];
    
    // Filename Handling
    let filenameManual = false;
    let lastAutoFilename = '';

    const stripExt = (name) => (typeof name === 'string') ? name.replace(/\.[^.]+$/, '') : '';
    const sanitizeBase = (s) => s.toLowerCase().replace(/[^a-z0-9\-_.\s]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^[-_.]+|[-_.]+$/g, '');

    const tokenizeName = (name) => stripExt(name).toLowerCase().split(/[-_\s]+/).filter(Boolean);
    const isPrefixToken = (tok) => /^(?:[a-z]|[0-9]{1,3}|[a-z][0-9]{0,2}|track|tr|take|tk|v|ver)$/.test(tok);
    const VARIANT_TOKENS = new Set(['mono','stereo','binaural','atmos','dolby','mix','session','master','demo','edit','live','instrumental','acapella','alt','alternate','radio','club','extended','short','version','remix']);
    const isVariantToken = (tok) => VARIANT_TOKENS.has(tok) || /^(?:v|ver|mix|session|version|take)\d+$/.test(tok) || /^\d+ch$/.test(tok);

    const extractTitleTokens = (name) => {
        const tokens = tokenizeName(name);
        while (tokens.length && isPrefixToken(tokens[0])) tokens.shift();
        const out = [];
        for (let i = 0; i < tokens.length; i++) {
            const t = tokens[i];
            if (isVariantToken(t)) break;
            out.push(t);
        }
        if (out.length === 0 && tokens.length) out.push(tokens[0]);
        return out;
    };

    const commonTitleFromFiles = () => {
        const names = fileSlots.filter(f => f).map(f => f.name);
        if (!names.length) return [];
        const lists = names.map(n => extractTitleTokens(n));
        let common = [];
        const first = lists[0];
        for (let i = 0; i < first.length; i++) {
            const tok = first[i];
            if (lists.every(lst => lst[i] === tok)) common.push(tok); else break;
        }
        if (!common.length) return first;
        return common;
    };

    const getCurrentTotalChannels = () => {
        let maxIdx = -1;
        for (let i = 0; i < MAX_SLOTS; i++) {
            if (fileSlots[i]) maxIdx = i;
        }
        if (maxIdx < 0) return 0;
        return Engine.calculateTotalChannels(maxIdx + 1);
    };

    const buildAutoFilename = () => {
        const titleTokens = commonTitleFromFiles();
        const title = sanitizeBase(titleTokens.join('-')) || 'track';
        const ch = getCurrentTotalChannels() || 2;
        return `${title}-${ch}ch.flac`;
    };

    const isValidFilename = (name) => {
        if (typeof name !== 'string') return { ok: true, msg: '' };
        const trimmed = name.trim();
        if (trimmed.length === 0) return { ok: true, msg: '' };
        if (!/\.flac$/i.test(trimmed)) return { ok: false, msg: 'Muss auf .flac enden' };
        const base = trimmed.replace(/\.flac$/i, '');
        if (!/^[A-Za-z0-9._-]+$/.test(base)) return { ok: false, msg: 'Nur Buchstaben, Zahlen, -, _ und . erlaubt' };
        return { ok: true, msg: '' };
    };

    const validateFilenameInput = () => {
        const $in = $get('flac-filename');
        if (!$in.length) return;
        const val = ($in.val() || '').toString();
        const res = isValidFilename(val);
        const $note = $in.next('.config-note');
        if (res.ok) {
            $in.removeClass('thespl-input-error');
            if ($note.length) {
                $note.text('Auto-generated from loaded files; editable before upload.').css('color', '#8b949e');
            }
        } else {
            $in.addClass('thespl-input-error');
            if ($note.length) {
                $note.text(res.msg).css('color', '#d63638');
            }
        }
    };

    const updateFilenameSuggestion = () => {
        const $in = $get('flac-filename');
        if (!$in.length) return;
        const hasFiles = fileSlots.some(f => f);
        if (!hasFiles) { $in.val(''); lastAutoFilename = ''; return; }
        const auto = buildAutoFilename();
        const val = ($in.val() || '').toString();
        const empty = val.trim() === '';
        if (!filenameManual || val === lastAutoFilename || empty) {
            $in.val(auto);
            lastAutoFilename = auto;
        }
        validateFilenameInput();
        adjustFilenameFieldWidth();
    };

    const adjustFilenameFieldWidth = () => {
        const $in = $get('flac-filename');
        if (!$in.length) return;
        const val = ($in.val() || '').toString();
        const ch = Math.max(24, Math.min(160, val.length + 3));
        $in.css('width', ch + 'ch');
    };

    const log = (s, level='info') => {
        const ts = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 });
        const colors = { error: '#f85149', success: '#3fb950', warn: '#d29922', debug: '#79c0a2', info: '#58a6ff' };
        const color = colors[level] || colors.info;
        const htmlLine = `<div class="log-line" style="color:${color}">[${ts}] ${level.toUpperCase()}: ${s}</div>`;
        
        logHtml.push(htmlLine);
        const $log = $get('log');
        $log.html(logHtml.join(''));
        $log.scrollTop($log[0].scrollHeight);
        console.log(`[THESPL Muxer] ${level.toUpperCase()}: ${s}`);
    };

    const formatTime = (seconds) => {
        if (!isFinite(seconds) || seconds < 0) return '--:--';
        const m = Math.floor(seconds / 60);
        const s = Math.floor(seconds % 60);
        return `${m}:${s.toString().padStart(2, '0')}`;
    };

    const setProgress = (p, t='') => {
        $get('progress').show();
        $get('progress-fill').css('width', p + '%');
        $get('progress-text').text(t || `${Math.round(p)}%`);
    };

    // --- Memory Management ---
    const freeMemory = () => {
        if (!ENABLE_MEMORY_OPTIMIZATION) return;
        if (generatedBlobs.length > 0) {
            generatedBlobs.forEach(url => URL.revokeObjectURL(url));
            generatedBlobs = [];
        }
        $get('result').empty();
    };

    const createPersistentBlob = (file, slotIndex) => {
        if (blobUrls[slotIndex]) URL.revokeObjectURL(blobUrls[slotIndex]);
        blobUrls[slotIndex] = URL.createObjectURL(file);
    };

    const revokePersistentBlob = (slotIndex) => {
        if (blobUrls[slotIndex]) {
            URL.revokeObjectURL(blobUrls[slotIndex]);
            blobUrls[slotIndex] = null;
        }
    };

    // --- Slot Management ---
    const normalizeSlots = () => {
        const validItems = [];
        for (let i = 0; i < MAX_SLOTS; i++) {
            if (fileSlots[i] !== null) {
                validItems.push({
                    file: fileSlots[i],
                    blobUrl: blobUrls[i],
                    channels: channelCounts[i],
                    sr: sampleRates[i],
                    bd: bitDepths[i],
                    dur: slotDurationsSeconds[i]
                });
            }
        }

        fileSlots.fill(null);
        blobUrls.fill(null);
        channelCounts.fill(null);
        sampleRates.fill(null);
        bitDepths.fill(null);
        slotDurationsSeconds.fill(null);

        for (let i = 0; i < validItems.length && i < MAX_SLOTS; i++) {
            fileSlots[i] = validItems[i].file;
            blobUrls[i] = validItems[i].blobUrl;
            channelCounts[i] = validItems[i].channels;
            sampleRates[i] = validItems[i].sr;
            bitDepths[i] = validItems[i].bd;
            slotDurationsSeconds[i] = validItems[i].dur;
        }

        const analysis = getMuxerAnalysis();
        for (let i = 0; i < MAX_SLOTS; i++) renderFileSlot(i, analysis);
        updateStatus();
        updateFilenameSuggestion();
    };

    const renderFileSlot = (slotIndex, analysis) => {
        const $slot = $container.find(`.file-slot[data-slot="${slotIndex}"]`);
        if (!$slot.length) return;

        const $dropZone = $slot.find('.drop-zone');
        const $fileInfo = $slot.find('.file-info');

        const muxerAnalysis = analysis || getMuxerAnalysis();
        const slotState = muxerAnalysis.slotStates[slotIndex];

        $slot.removeClass('has-file loading error warning dragging duplicate-warning');

        if (slotState && slotState.hasFile) {
            $slot.attr('draggable', 'true');
            $dropZone.addClass('hidden');
            $fileInfo.show();

            if (Array.isArray(slotState.slotClasses) && slotState.slotClasses.length) {
                $slot.addClass(slotState.slotClasses.join(' '));
            }

            if ('loading' === slotState.status) {
                $fileInfo.html(`<div class="file-badge loading"><div class="file-name">${escapeHtml(slotState.file.name)}</div><div class="file-size">${escapeHtml(slotState.message)}</div></div>`);
            } else {
                const badgeClass = slotState.badgeClass ? ` ${slotState.badgeClass}` : '';
                const badgeCursor = 'ready' === slotState.status ? ' style="cursor:grab"' : '';
                $fileInfo.html(`<div class="file-badge${badgeClass}"${badgeCursor}><span class="remove-file" data-slot="${slotIndex}">×</span><div class="file-name">${escapeHtml(slotState.file.name)}</div><div class="file-size">${escapeHtml(slotState.message)}</div></div>`);
            }
        } else {
            $slot.removeAttr('draggable');
            $dropZone.removeClass('hidden');
            $fileInfo.hide();
        }
    };

    const validateConfigOptions = () => {
        const analysis = getMuxerAnalysis();
        const negotiatedProfile = analysis.negotiatedProfile;
        const sr44 = $get('sr-44100');
        const sr48 = $get('sr-48000');
        const bd16 = $get('bd-16');
        const bd24 = $get('bd-24');
        const $srNote = $get('sr-note');
        const $bdNote = $get('bd-note');
        const mutedColor = '#8b949e';
        const warningColor = '#d29922';
        const errorColor = '#f85149';
        const successColor = '#3fb950';

        if (!ALLOW_HIGH_RES) {
            sr48.prop('disabled', true).prop('checked', false);
            sr48.parent().addClass('disabled-option');
            sr44.prop('disabled', false).prop('checked', true);
            sr44.parent().removeClass('disabled-option');

            bd24.prop('disabled', true).prop('checked', false);
            bd24.parent().addClass('disabled-option');
            bd16.prop('disabled', false).prop('checked', true);
            bd16.parent().removeClass('disabled-option');

            $srNote.text(
                analysis.hasSampleRateBlocker ?
                    getI18nText('noteFreeSampleRateBlocked', 'Unsupported input loaded. Free Muxer accepts 44.1 kHz stereo WAV only.') :
                    getI18nText('noteFreeSampleRate', 'Free Muxer accepts 44.1 kHz stereo WAV only.')
            ).css('color', analysis.hasSampleRateBlocker ? errorColor : mutedColor);

            $bdNote.text(
                analysis.hasBitDepthBlocker ?
                    getI18nText('noteFreeBitDepthBlocked', 'Unsupported input loaded. Free Muxer accepts 16-bit stereo WAV only.') :
                    getI18nText('noteFreeBitDepth', 'Free Muxer accepts 16-bit stereo WAV only.')
            ).css('color', analysis.hasBitDepthBlocker ? errorColor : mutedColor);

            updateSizeEstimate();
        } else if (!negotiatedProfile) {
            sr44.prop('disabled', false);
            sr48.prop('disabled', false);
            bd16.prop('disabled', false);
            bd24.prop('disabled', false);
            sr44.parent().removeClass('disabled-option');
            sr48.parent().removeClass('disabled-option');
            bd16.parent().removeClass('disabled-option');
            bd24.parent().removeClass('disabled-option');

            $srNote.text(getI18nText('noteProSampleRateDefault', 'Pro Muxer accepts stereo WAV up to 48 kHz.')).css('color', mutedColor);
            $bdNote.text(getI18nText('noteProBitDepthDefault', 'Pro Muxer accepts stereo WAV up to 24-bit.')).css('color', mutedColor);
        } else {
            const targetSampleRate = negotiatedProfile.sampleRate;
            const targetBitDepth = negotiatedProfile.bitDepth;
            const isSampleRateReduced = negotiatedProfile.hasSampleRateReduction;
            const isBitDepthReduced = negotiatedProfile.hasBitDepthReduction;

            sr44.prop('disabled', false);
            sr44.parent().removeClass('disabled-option');
            sr48.prop('disabled', targetSampleRate < 48000).prop('checked', false);
            sr48.parent().toggleClass('disabled-option', targetSampleRate < 48000);

            if (targetSampleRate >= 48000) {
                sr48.prop('checked', true);
            } else {
                sr44.prop('checked', true);
            }

            bd16.prop('disabled', false);
            bd16.parent().removeClass('disabled-option');
            bd24.prop('disabled', targetBitDepth < 24).prop('checked', false);
            bd24.parent().toggleClass('disabled-option', targetBitDepth < 24);

            if (targetBitDepth >= 24) {
                bd24.prop('checked', true);
            } else {
                bd16.prop('checked', true);
            }

            if (analysis.hasSampleRateBlocker) {
                $srNote.text(getI18nText('noteProSampleRateBlocked', 'Unsupported input loaded. Pro Muxer supports up to 48 kHz.')).css('color', errorColor);
            } else if (isSampleRateReduced) {
                $srNote.text(getI18nText('noteProSampleRateReduced', 'Mixed sample rates detected. Export target reduced to %s.', [formatSampleRateLabel(targetSampleRate)])).css('color', warningColor);
            } else if (targetSampleRate < 48000) {
                $srNote.text(getI18nText('noteProSampleRateNoUpsampling', '48 kHz export is unavailable because the loaded files are 44.1 kHz. Upsampling remains disabled.')).css('color', mutedColor);
            } else {
                $srNote.text(getI18nText('noteProSampleRateReady', '48 kHz export is available for the current source files.')).css('color', successColor);
            }

            if (analysis.hasBitDepthBlocker) {
                $bdNote.text(getI18nText('noteProBitDepthBlocked', 'Unsupported input loaded. Pro Muxer supports up to 24-bit.')).css('color', errorColor);
            } else if (isBitDepthReduced) {
                $bdNote.text(getI18nText('noteProBitDepthReduced', 'Mixed bit depths detected. Export target reduced to %s.', [formatBitDepthLabel(targetBitDepth)])).css('color', warningColor);
            } else if (targetBitDepth < 24) {
                $bdNote.text(getI18nText('noteProBitDepthNoUpsampling', '24-bit export is unavailable because the loaded files are 16-bit. Upsampling remains disabled.')).css('color', mutedColor);
            } else {
                $bdNote.text(getI18nText('noteProBitDepthReady', '24-bit export is available for the current source files.')).css('color', successColor);
            }
        }

        updateSizeEstimate();

        if (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars.max_upload_size) {
            const limitBytes = parseInt(thespl_muxer_vars.max_upload_size);
            $get('upload-limit-info').text(`Server Upload Limit: ${formatBytes(limitBytes)}`);
        }
    };

    // --- Helper for Button State ---
    const updateButtonState = () => {
        const analysis = getMuxerAnalysis();
        const disabled = !libflacReady || isConverting || !analysis.canRun;
        $get('run').prop('disabled', disabled);

        if (disabled && analysis.loadedCount > 0) {
            console.log(`[THESPL Debug] Button Disabled. Ready:${libflacReady}, Converting:${isConverting}, CanRun:${analysis.canRun}, Blocking:${analysis.blockingCount}, Pending:${analysis.pendingCount}`);
        }
    };

    const updateStatus = () => {
        const analysis = getMuxerAnalysis();
        let baseStatus = '';
        let statusClass = 'status-idle';

        if (analysis.loadedCount === 0) {
            baseStatus = `No files loaded - load up to ${MAX_SLOTS} stereo WAV files.`;
        } else if (analysis.pendingCount > 0) {
            baseStatus = getI18nText('statusAnalyzing', 'Analyzing loaded files...');
        } else if (analysis.blockingState) {
            baseStatus = analysis.blockingState.message;
            statusClass = 'status-error';
        } else {
            const mode = analysis.compatibleCount * 2;
            baseStatus = getI18nText('statusReady', '%1$s files loaded -> %2$s-channel FLAC output ready.', [analysis.compatibleCount, mode]);

            if (analysis.compatibleCount > 1) {
                baseStatus += ' ' + getI18nText('statusTipReorder', 'Tip: Drag & Drop to reorder.');
            }

            if (analysis.hasOutputReduction && analysis.negotiatedProfile) {
                baseStatus += ' ' + getI18nText(
                    'statusReduction',
                    'Export target reduced to %1$s / %2$s.',
                    [formatSampleRateLabel(analysis.negotiatedProfile.sampleRate), formatBitDepthLabel(analysis.negotiatedProfile.bitDepth)]
                );
                statusClass = 'status-warning';
            } else {
                statusClass = 'status-ready';
            }

            if (analysis.hasDuplicates) {
                baseStatus += ' ' + getI18nText('statusDuplicate', 'Duplicate files detected.');
                statusClass = 'status-warning';
            }
        }

        const editionText = getEditionStatusText();
        const capabilityText = getCapabilitySummary();
        let waveformPart = '';

        if (analysis.loadedCount > 0 && analysis.canRun) {
            evaluateWaveformPolicy();
            updateWaveformOutputBox();
            if (waveformReason && waveformReason.length) {
                waveformPart = ' Waveform: ' + waveformReason;
            }
        } else {
            updateWaveformOutputBox();
        }
        
        const statusText = `${editionText} - ${capabilityText} - ${baseStatus}${waveformPart}`;
        
        $get('status').text(statusText);
        $container.find('.status-display').removeClass('status-idle status-ready status-error status-warning').addClass(statusClass);

        validateConfigOptions();
        updateFilenameSuggestion();
    };

    const updateSizeEstimate = () => {
        const $el = $get('size-estimate');
        const comp = Math.max(0, Math.min(8, parseInt($get('comp').val())));
        $get('comp-val').text(comp);
        
        const analysis = getMuxerAnalysis();
        if (analysis.loadedCount === 0 || analysis.pendingCount > 0 || analysis.blockingCount > 0) {
            $el.text('—');
            updateButtonState();
            return;
        }

        const targetSR = analysis.negotiatedProfile ? analysis.negotiatedProfile.sampleRate : (parseInt($container.find('input[name="sample-rate"]:checked').val(), 10) || 44100);
        const targetBD = analysis.negotiatedProfile ? analysis.negotiatedProfile.bitDepth : (parseInt($container.find('input[name="bit-depth"]:checked').val(), 10) || 16);
        
        let totalProjectedPCM = 0;
        
        for (let i = 0; i < MAX_SLOTS; i++) {
            const slotState = analysis.slotStates[i];
            if (slotState && slotState.hasFile && slotState.status === 'ready') {
                const f = fileSlots[i];
                const inSR = sampleRates[i] || 44100;
                const inBD = bitDepths[i] || 16;
                const inputPCMBytes = Math.max(0, f.size - 44);
                const srScale = targetSR / inSR;
                const bdScale = targetBD / inBD;
                const projectedBytes = inputPCMBytes * srScale * bdScale;
                totalProjectedPCM += projectedBytes;
            }
        }
        
        // FLAC Compression Estimation
        // Adjusted based on real-world test (Pink Noise 4ch):
        // Estimated: ~111 MB (lvl 8) -> Real: 135.3 MB
        // Diff: +21%
        // Pink Noise is worst-case for compression (randomness). Music/Speech compresses better.
        // We adjust ratios up to be safer (avoid "Upload Limit Exceeded" surprise).
        const estimateRatios = [0.85, 0.82, 0.79, 0.77, 0.75, 0.73, 0.71, 0.69, 0.67];
        const ratio = estimateRatios[comp];
        
        const estBytes = totalProjectedPCM * ratio;
        const estMB = estBytes / (1024 * 1024);
        
        // Use a wider safety margin on the upper bound for safety
        const low = estMB * 0.95;
        const high = estMB * 1.15;
        
        $el.text(`${low.toFixed(2)} – ${high.toFixed(2)} MB (lvl ${comp})`);
        $el.css('color', '#e6edf3');

        // Check Chunk Limit: only compare chunk size with server upload limit.
        if (typeof thespl_muxer_vars !== 'undefined' && thespl_muxer_vars.max_upload_size) {
            const limitBytes = parseInt(thespl_muxer_vars.max_upload_size);
            const chunkSize = getChunkSize();

            if (chunkSize > limitBytes) {
                $get('upload-limit-info').html(
                    `<span style="color:#ff7b72">⚠️ Chunk size (${formatBytes(chunkSize)}) exceeds server limit (${formatBytes(limitBytes)}). Upload may fail.</span>`
                );
            } else {
                $get('upload-limit-info').text(`Server upload limit per request is ${formatBytes(limitBytes)}.`);
                $get('config-limit-warning').hide();
            }
        }
        
        // Finalize button state
        updateButtonState();
        updateRadioVisualSelection();
    };

    const updateRadioVisualSelection = () => {
        const $srLabels = $container.find('#sr-group label.radio-option');
        const $bdLabels = $container.find('#bd-group label.radio-option');
        $srLabels.removeClass('is-selected');
        $bdLabels.removeClass('is-selected');
        const $srSel = $container.find('#sr-group label.radio-option input[name="sample-rate"]:checked');
        const $bdSel = $container.find('#bd-group label.radio-option input[name="bit-depth"]:checked');
        if ($srSel.length) $srSel.closest('label.radio-option').addClass('is-selected');
        if ($bdSel.length) $bdSel.closest('label.radio-option').addClass('is-selected');
    };

    // --- WAV Processing ---
    const analyzeWavHeader = (file) => {
        return new Promise((resolve, reject) => {
            if (!file || (file.type !== 'audio/wav' && !file.name.toLowerCase().endsWith('.wav'))) {
                reject(new Error('Not a WAV file'));
                return;
            }
            const reader = new FileReader();
            reader.onload = (e) => {
                const buffer = new Uint8Array(e.target.result);
                if (buffer.length < 44 || String.fromCharCode(...buffer.slice(0, 4)) !== 'RIFF' || String.fromCharCode(...buffer.slice(8, 12)) !== 'WAVE') {
                    reject(new Error('Invalid WAV header'));
                    return;
                }
                // ... (Simplified parsing for brevity, assuming standard WAVs or using the full logic from muxxer-pro)
                // Using the robust logic from muxxer-pro.html:
                let pos = 12;
                while (pos < Math.min(buffer.length - 8, 4096)) {
                    const chunkId = String.fromCharCode(...buffer.slice(pos, pos + 4));
                    const chunkSize = (buffer[pos + 7] << 24) | (buffer[pos + 6] << 16) | (buffer[pos + 5] << 8) | buffer[pos + 4];
                    if (chunkId === 'fmt ') {
                        const fmtDataStart = pos + 8;
                        const nChannels = buffer[fmtDataStart + 2] | (buffer[fmtDataStart + 3] << 8);
                        const sampleRate = buffer[fmtDataStart + 4] | (buffer[fmtDataStart + 5] << 8) | (buffer[fmtDataStart + 6] << 16) | (buffer[fmtDataStart + 7] << 24);
                        const formatTag = buffer[fmtDataStart] | (buffer[fmtDataStart + 1] << 8);
                        let bitsPerSample = 0;
                        if (formatTag === 1 || chunkSize <= 16) {
                            bitsPerSample = buffer[fmtDataStart + 14] | (buffer[fmtDataStart + 15] << 8);
                        } else if (formatTag === 0xFFFE && chunkSize >= 40) {
                            bitsPerSample = buffer[fmtDataStart + 18] | (buffer[fmtDataStart + 19] << 8);
                        } else {
                            bitsPerSample = buffer[fmtDataStart + 14] | (buffer[fmtDataStart + 15] << 8);
                        }
                        if (nChannels === 0 || sampleRate === 0 || bitsPerSample === 0) {
                            reject(new Error(`Invalid WAV: channels=${nChannels}, sr=${sampleRate}, bits=${bitsPerSample}`));
                            return;
                        }
                        let durationSeconds = 0;
                        if (file && typeof file.size === 'number') {
                            const headerBytes = 44;
                            const bytesPerSample = (bitsPerSample / 8) * nChannels;
                            if (bytesPerSample > 0 && sampleRate > 0) {
                                const dataBytes = Math.max(0, file.size - headerBytes);
                                const frames = dataBytes / bytesPerSample;
                                if (isFinite(frames) && frames > 0) {
                                    durationSeconds = frames / sampleRate;
                                }
                            }
                        }
                        resolve({ channels: nChannels, sampleRate, bitsPerSample, durationSeconds });
                        return;
                    }
                    pos += 8 + chunkSize;
                    if (chunkSize % 2 === 1) pos++;
                    if (pos >= buffer.length) break;
                }
                reject(new Error('fmt chunk not found'));
            };
            reader.onerror = () => reject(new Error('File read error'));
            reader.readAsArrayBuffer(file.slice(0, 4096));
        });
    };

    const validateAndSetHeader = async (slotIndex, file) => {
        try {
            const header = await analyzeWavHeader(file);
            channelCounts[slotIndex] = header.channels;
            sampleRates[slotIndex] = header.sampleRate;
            bitDepths[slotIndex] = header.bitsPerSample;
            slotDurationsSeconds[slotIndex] = header.durationSeconds;
            log(`Slot ${slotIndex + 1} validated: ${header.channels}ch, ${header.sampleRate / 1000}kHz, ${header.bitsPerSample}-bit`, 'info');
            
            if (header.channels !== 2) {
                log(`ERROR: Slot ${slotIndex + 1} must be 2-channel, got ${header.channels}`,'error');
                // Ensure UI updates to show error
                renderFileSlot(slotIndex);
                slotDurationsSeconds[slotIndex] = null;
                return false;
            }

            const formatValid = Engine.validateFormat(header.sampleRate, header.bitsPerSample);
            if (formatValid !== true) {
                log(`ERROR: ${buildUnsupportedFormatMessage(header.sampleRate, header.bitsPerSample)}`, 'error');
                renderFileSlot(slotIndex);
                return false;
            }

            return true;
        } catch (e) {
            log(`ERROR validating slot ${slotIndex + 1}: ${e.message}`, 'error');
            // Mark as error state for UI
            channelCounts[slotIndex] = -1;
            sampleRates[slotIndex] = null;
            bitDepths[slotIndex] = null;
            slotDurationsSeconds[slotIndex] = null;
            renderFileSlot(slotIndex);
            return false;
        }
    };

    // --- PCM Extraction & Resampling (Full Logic) ---
    const extractPCM = (wavFile) => {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => {
                try {
                    const buffer = new Uint8Array(e.target.result);
                    let pos = 12;
                    let dataChunkPos = null;
                    let dataSize = 0;
                    let fmtPos = null;
                    
                    while (pos < buffer.length - 8) {
                        const chunkId = String.fromCharCode(...buffer.slice(pos, pos + 4));
                        const chunkSize = (buffer[pos + 7] << 24) | (buffer[pos + 6] << 16) | (buffer[pos + 5] << 8) | buffer[pos + 4];
                        if (chunkId === 'fmt ') fmtPos = pos;
                        if (chunkId === 'data') {
                            dataChunkPos = pos + 8;
                            dataSize = chunkSize;
                            break;
                        }
                        pos += 8 + chunkSize;
                        if (chunkSize % 2 === 1) pos++;
                    }
                    
                    if (dataChunkPos === null || !fmtPos) reject(new Error('Chunks missing'));
                    
                    const formatTag = buffer[fmtPos + 8] | (buffer[fmtPos + 9] << 8);
                    const bitsPerSample = buffer[fmtPos + 22] | (buffer[fmtPos + 23] << 8);
                    const bytesPerSample = bitsPerSample / 8;
                    const samplesCount = dataSize / bytesPerSample;
                    const output = new Int32Array(samplesCount);
                    
                    if (bitsPerSample === 16) {
                        for (let i = 0; i < samplesCount; i++) {
                            const off = dataChunkPos + (i * 2);
                            const s16 = (buffer[off + 1] << 8) | buffer[off];
                            output[i] = s16 > 32767 ? s16 - 65536 : s16;
                        }
                    } else if (bitsPerSample === 24) {
                        for (let i = 0; i < samplesCount; i++) {
                            const off = dataChunkPos + (i * 3);
                            let val = (buffer[off + 2] << 24) | (buffer[off + 1] << 16) | (buffer[off] << 8);
                            output[i] = val >> 8;
                        }
                    } else if (bitsPerSample === 32) {
                        // Support for 32-bit Integer and 32-bit Float (simplified)
                        const view = new DataView(buffer.buffer, dataChunkPos, dataSize);
                        const isFloat = (formatTag === 3); // Check formatTag from fmt chunk
                        for (let i = 0; i < samplesCount; i++) {
                            if (isFloat) {
                                const f32 = view.getFloat32(i * 4, true);
                                output[i] = Math.round(f32 * 2147483647);
                            } else {
                                output[i] = view.getInt32(i * 4, true);
                            }
                        }
                    } else {
                        reject(new Error(`Unsupported bit depth: ${bitsPerSample}`));
                    }
                    resolve(output);
                } catch (err) { reject(err); }
            };
            reader.onerror = () => reject(new Error('Read failed'));
            reader.readAsArrayBuffer(wavFile);
        });
    };

    const resamplePCM = (input, srcRate, destRate, channels) => {
        if (srcRate === destRate) return input;
        const ratio = srcRate / destRate;
        const inputFrames = input.length / channels;
        const outputFrames = Math.floor(inputFrames / ratio);
        const output = new Int32Array(outputFrames * channels);
        
        for (let frame = 0; frame < outputFrames; frame++) {
            const srcPos = frame * ratio;
            const index = Math.floor(srcPos);
            const frac = srcPos - index;
            
            for (let ch = 0; ch < channels; ch++) {
                const i1 = (index * channels) + ch;
                const i2 = i1 + channels;
                const v1 = input[i1] || 0;
                const v2 = (index + 1 < inputFrames) ? (input[i2] || 0) : v1;
                output[(frame * channels) + ch] = Math.round(v1 + (v2 - v1) * frac);
            }
        }
        return output;
    };

    // --- Interaction ---
    const handleFiles = async (files, slotIndex) => {
        if (!files.length) return;
        
        try {
            files.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }));
        } catch (e) {}

        // FIXED (Session 19.12.2025): Respect the dropped slot index!
        // Logic matched to Prototype:
        // 1. Try to use the target slot (slotIndex).
        // 2. If target is occupied, look for the first empty slot.
        // 3. If all full, overwrite the target slot.
        
        // Always fill from the leftmost empty slot, ignoring the clicked slot for packing.
        // Single-file and multi-file behave identically: 1..N contiguous without gaps.
        const findFirstEmpty = () => fileSlots.findIndex(s => s === null);
        let nextSlot = findFirstEmpty();
        if (nextSlot === -1) {
            // No empty slots: fallback to target slot if valid, otherwise slot 0 (overwrite behavior)
            nextSlot = (typeof slotIndex === 'number' && slotIndex >= 0 && slotIndex < MAX_SLOTS) ? slotIndex : 0;
        }

        for (let f = 0; f < files.length; f++) {
            if (nextSlot >= 0 && nextSlot < MAX_SLOTS) {
                fileSlots[nextSlot] = files[f];
                channelCounts[nextSlot] = null; // Mark as loading

                renderFileSlot(nextSlot);
                await validateAndSetHeader(nextSlot, files[f]);
                renderFileSlot(nextSlot);

                // Find next empty slot for the next file
                nextSlot = findFirstEmpty();
                if (nextSlot === -1) {
                    // No more slots available; remaining files are skipped
                    if (f + 1 < files.length) {
                        log(`Skipped ${files.length - (f + 1)} file(s) - No slots available.`, 'warn');
                    }
                    break;
                }
            } else {
                log(`Skipped '${files[f].name}' - No slots available.`, 'warn');
            }
        }
        
        normalizeSlots();
    };

    $container.on('click', '.remove-file', function(e) {
        e.stopPropagation();
        const idx = $(this).data('slot');
        fileSlots[idx] = null;
        channelCounts[idx] = null;
        sampleRates[idx] = null;
        bitDepths[idx] = null;
        slotDurationsSeconds[idx] = null;
        renderFileSlot(idx);
        normalizeSlots();
        log(`File removed from slot ${idx + 1}`, 'info');
    });

    $container.on('click', '.drop-zone', function() {
        const $slot = $(this).closest('.file-slot');
        const idx = $slot.data('slot');
        if (isSlotLocked(idx)) {
            return;
        }
        $slot.find('input[type="file"]').click();
    });

    $container.on('keydown', '.drop-zone', function(e) {
        if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            const $slot = $(this).closest('.file-slot');
            const idx = $slot.data('slot');
            if (isSlotLocked(idx)) {
                return;
            }
            $slot.find('input[type="file"]').click();
        }
    });

    $container.on('change', 'input[type="file"]', function(e) {
        const idx = $(this).closest('.file-slot').data('slot');
        handleFiles(Array.from(e.target.files || []), idx);
        $(this).val('');
    });

    // --- Drag & Drop (Reordering + File Drop) ---
    $container.on('dragstart', '.file-slot', function(e) {
        const slotIdx = $(this).data('slot');
        if (isSlotLocked(slotIdx)) {
            e.preventDefault();
            return;
        }
        if (!fileSlots[slotIdx]) {
            e.preventDefault();
            return;
        }
        e.originalEvent.dataTransfer.setData('text/plain', slotIdx);
        e.originalEvent.dataTransfer.effectAllowed = 'move';
        $(this).addClass('dragging');
    });

    $container.on('dragend', '.file-slot', function(e) {
        $(this).removeClass('dragging');
        $container.find('.file-slot').removeClass('drag-over');
    });

    $container.on('dragover', '.file-slot', function(e) {
        e.preventDefault();
        e.originalEvent.dataTransfer.dropEffect = 'move';
        $(this).addClass('drag-over');
    });

    $container.on('dragleave', '.file-slot', function(e) {
        $(this).removeClass('drag-over');
    });

    $container.on('drop', '.file-slot', function(e) {
        e.preventDefault();
        $(this).removeClass('drag-over');

        const targetSlot = $(this).data('slot');
        if (isSlotLocked(targetSlot)) {
            return;
        }
        const sourceSlotStr = e.originalEvent.dataTransfer.getData('text/plain');

        // Check if it's an internal drag (reordering)
        if (sourceSlotStr && !isNaN(parseInt(sourceSlotStr))) {
            const sourceSlot = parseInt(sourceSlotStr);
            if (sourceSlot !== targetSlot) {
                // Swap Logic
                const tmpFile = fileSlots[sourceSlot];
                fileSlots[sourceSlot] = fileSlots[targetSlot];
                fileSlots[targetSlot] = tmpFile;

                const tmpBlob = blobUrls[sourceSlot];
                blobUrls[sourceSlot] = blobUrls[targetSlot];
                blobUrls[targetSlot] = tmpBlob;

                const tmpCh = channelCounts[sourceSlot];
                channelCounts[sourceSlot] = channelCounts[targetSlot];
                channelCounts[targetSlot] = tmpCh;

                const tmpSR = sampleRates[sourceSlot];
                sampleRates[sourceSlot] = sampleRates[targetSlot];
                sampleRates[targetSlot] = tmpSR;

                const tmpBD = bitDepths[sourceSlot];
                bitDepths[sourceSlot] = bitDepths[targetSlot];
                bitDepths[targetSlot] = tmpBD;

                renderFileSlot(sourceSlot);
                renderFileSlot(targetSlot);

                // FORCE NO GAPS: Normalize slots after swap
                // This ensures that if we swapped into an empty slot creating a gap,
                // it snaps back to fill from Slot 1.
                normalizeSlots();
                log(`Swapped Slot ${sourceSlot+1} with Slot ${targetSlot+1}`, 'info');
            }
        } else {
            // External File Drop
            const files = e.originalEvent.dataTransfer.files;
            if (files && files.length > 0) {
                handleFiles(Array.from(files), targetSlot);
            }
        }
    });

    $container.on('input', '#comp', updateSizeEstimate);
    $container.on('change', 'input[name="sample-rate"], input[name="bit-depth"]', function(){ updateSizeEstimate(); updateRadioVisualSelection(); });
    $container.on('change', '#upload-chunk-size', updateSizeEstimate);
    $container.on('input change', '#flac-filename', function(){ filenameManual = true; validateFilenameInput(); adjustFilenameFieldWidth(); updateButtonState(); updateStatus(); });

    $container.on('change', '#thespl-hide-security-verification', function() {
        if ($(this).is(':checked')) {
            $.ajax({
                url: thespl_muxer_vars.ajax_url,
                type: 'POST',
                data: {
                    action: 'thespl_hide_security_verification',
                    nonce: thespl_muxer_vars.nonce
                },
                success: function(res) {
                    if (res.success) {
                        $('#thespl-security-verification-box').fadeOut();
                        log('Security verification hidden and preference saved.', 'info');
                    }
                }
            });
        }
    });

    $container.on('click', '#save-upload-config', function() {
        const $btn = $(this);
        const oldText = $btn.text();
        const subdir = getUploadSubdir();
        const chunkSize = getChunkSize();

        $btn.prop('disabled', true).text(thesplMuxerI18n.sending || 'Saving...');

        $.ajax({
            url: thespl_muxer_vars.ajax_url,
            type: 'POST',
            data: {
                action: 'thespl_save_settings',
                nonce: thespl_muxer_vars.nonce,
                subdir: subdir,
                chunk_size: chunkSize
            },
            success: function(res) {
                if (res.success) {
                    log('Configuration saved as default', 'success');
                    $btn.text('✅ ' + (thesplMuxerI18n.saved || 'Saved'));
                } else {
                    log('Error saving configuration: ' + (res.data.message || 'Unknown error'), 'error');
                    $btn.text(oldText);
                }
            },
            error: function() {
                log('Network error while saving configuration', 'error');
                $btn.text(oldText);
            },
            complete: function() {
                setTimeout(() => {
                    $btn.prop('disabled', false).text(oldText);
                }, 2000);
            }
        });
    });

    $get('reset').on('click', () => {
        if (isConverting) {
            if (confirm('Stop the current operation?')) {
                isCancelled = true;
                if (currentXHR) {
                    currentXHR.abort();
                    currentXHR = null;
                }
                log('Cancellation requested by user...', 'warn');
            }
            return;
        }

        freeMemory();
        fileSlots.fill(null);
        channelCounts.fill(null);
        sampleRates.fill(null);
        bitDepths.fill(null);
        slotDurationsSeconds.fill(null);
        waveformAllowed = false;
        waveformReason = '';
        for (let i = 0; i < MAX_SLOTS; i++) {
            renderFileSlot(i);
        }
        $get('result').empty();
        $get('progress').hide();
        updateStatus();
        filenameManual = false;
        lastAutoFilename = '';
        const $in = $get('flac-filename');
        if ($in.length) { $in.val(''); adjustFilenameFieldWidth(); }
        log('Reset complete', 'info');
    });

    // --- Cleanup Helper ---
    const cleanupTempFile = (fileName, subdir) => {
        return new Promise((resolve) => {
            log(`Cleaning up temp file for ${fileName}...`, 'info');
            $.ajax({
                url: thespl_muxer_vars.ajax_url,
                type: 'POST',
                data: {
                    action: 'thespl_cancel_upload',
                    nonce: thespl_muxer_vars.nonce,
                    subdir: subdir,
                    filename: fileName
                },
                success: function(res) {
                    if (res.success) {
                        log('Temp file cleaned up successfully', 'success');
                    } else {
                        log('Cleanup warning: ' + (res.data.message || 'Unknown error'), 'warn');
                    }
                    resolve();
                },
                error: function() {
                    log('Cleanup failed (Network error)', 'error');
                    resolve();
                }
            });
        });
    };

    // --- Chunk Upload Helper (Now Standard for All Users) ---
    const uploadFileInChunks = async (blob, fileName, subdir, progressStart = 0, progressRange = 100) => {
        const chunkSize = getChunkSize();
        const totalChunks = Math.ceil(blob.size / chunkSize);
        const startTime = Date.now();
        
        log(`Starting upload for ${fileName} (${formatBytes(blob.size)} in ${totalChunks} chunks)`, 'info');

        let lastResponse = null;

        try {
            for (let i = 0; i < totalChunks; i++) {
                if (isCancelled) {
                    await cleanupTempFile(fileName, subdir);
                    throw new Error("Upload cancelled by user");
                }

                const start = i * chunkSize;
                const end = Math.min(start + chunkSize, blob.size);
                const chunk = blob.slice(start, end);
                
                const formData = new FormData();
                formData.append('action', 'thespl_upload_muxed_files');
                formData.append('nonce', thespl_muxer_vars.nonce);
                formData.append('subdir', subdir);
                formData.append('chunk_size', chunkSize);
                formData.append('chunk_index', i);
                formData.append('total_chunks', totalChunks);
                formData.append('total_file_size', blob.size); // Send total size for server-side validation
                formData.append('file_name', fileName);
                formData.append('file_key', 'chunk_data');
                formData.append('chunk_data', chunk, fileName);

                lastResponse = await new Promise((resolve, reject) => {
                    currentXHR = $.ajax({
                        url: thespl_muxer_vars.ajax_url,
                        type: 'POST',
                        data: formData,
                        processData: false,
                        contentType: false,
                        success: function(res) {
                            currentXHR = null;
                            if (res.success) {
                                const chunkProgress = (i + 1) / totalChunks;
                                const globalProgress = progressStart + (chunkProgress * progressRange);
                                
                                // Calculate Stats
                                const elapsed = (Date.now() - startTime) / 1000;
                                const uploadedBytes = Math.min((i + 1) * chunkSize, blob.size);
                                const speed = elapsed > 0 ? uploadedBytes / elapsed : 0;
                                const remaining = blob.size - uploadedBytes;
                                const eta = speed > 0 ? remaining / speed : 0;
                                
                                const stats = `${formatBytes(uploadedBytes)}/${formatBytes(blob.size)} - ${formatBytes(speed)}/s - ETA: ${formatTime(eta)}`;
                                
                                setProgress(globalProgress, `Uploading ${fileName}: ${Math.round(chunkProgress * 100)}% (${stats})`);
                                resolve(res);
                            } else {
                                reject(new Error(res.data.message || 'Chunk upload failed'));
                            }
                        },
                        error: function(jqXHR, textStatus, errorThrown) {
                            currentXHR = null;
                            if (textStatus === 'abort') {
                                reject(new Error('Upload cancelled by user'));
                            } else {
                                reject(new Error(`Chunk ${i+1}/${totalChunks} failed: ${textStatus}`));
                            }
                        }
                    });
                });
            }
        } catch (e) {
            if (e.message === 'Upload cancelled by user') {
                await cleanupTempFile(fileName, subdir);
            }
            throw e;
        }
        
        return lastResponse ? lastResponse.data : null;
    };

    // --- Main Logic: Encoding & Uploading ---
    $get('run').on('click', async () => {
        if (isConverting || !libflacReady) return;

        const runAnalysis = getMuxerAnalysis();
        if (runAnalysis.pendingCount > 0) {
            alert(getI18nText('runBlockedAnalyzing', 'Please wait until file analysis is complete.'));
            return;
        }
        if (runAnalysis.blockingState) {
            alert(runAnalysis.blockingState.message);
            updateStatus();
            return;
        }
        if (runAnalysis.loadedCount === 0 || !runAnalysis.canRun) {
            alert(getI18nText('runBlockedNoFiles', 'Please load at least one compatible stereo WAV file before starting.'));
            return;
        }

		// Validate Subdirectory (Security Check)
		const subdir = getUploadSubdir().trim();
		if (!validateSubdirValue(subdir)) {
			alert('Please fix the subdirectory path before starting.');
			return;
		}

        if (ENABLE_MEMORY_OPTIMIZATION) freeMemory();
        
        isConverting = true;
        setUnloadGuard(true);
        isCancelled = false;
        $get('run').prop('disabled', true);
        $get('reset').text('Stop / Cancel').css('background-color', '#da3633').css('border-color', '#d73a49').css('color', '#ffffff');
        
        $get('result').empty();
        setProgress(1, 'Starting...');
        
        let channelData = [];
        
        try {
            const targetSR = runAnalysis.negotiatedProfile ? runAnalysis.negotiatedProfile.sampleRate : (parseInt($container.find('input[name="sample-rate"]:checked').val(), 10) || 44100);
            const targetBD = runAnalysis.negotiatedProfile ? runAnalysis.negotiatedProfile.bitDepth : (parseInt($container.find('input[name="bit-depth"]:checked').val(), 10) || 16);
            const comp = parseInt($get('comp').val());
            
            log(`Starting: Target ${targetSR}Hz, ${targetBD}-bit, Comp ${comp}`, 'info');
            
            const activeFiles = [];
            let maxSlotIndex = -1;
            for(let i=0; i<MAX_SLOTS; i++) {
                if (fileSlots[i]) {
                    activeFiles.push({ file: fileSlots[i], index: i });
                    if (i > maxSlotIndex) maxSlotIndex = i;
                }
            }
            
            if (activeFiles.length === 0) throw new Error("No files");
            evaluateWaveformPolicy();
            updateWaveformOutputBox();
            const waveformGenerationEnabled = waveformAllowed;

            // 1. Pre-calculate Size
            let maxFrames = 0;
            for (let i = 0; i < activeFiles.length; i++) {
                const { file } = activeFiles[i];
                const header = await analyzeWavHeader(file);
                const srcFrames = (file.size - 44) / (header.bitsPerSample/8 * header.channels); // Approx
                let destFrames = srcFrames;
                if (header.sampleRate !== targetSR) {
                    destFrames = Math.floor(srcFrames * (targetSR / header.sampleRate)) + 100;
                }
                if (destFrames > maxFrames) maxFrames = destFrames;
            }

            // 2. Allocate
            // FIXED (Anchor 1): Calculate channels based on Max Slot Index to allow gaps
            // If Slot 0 (Index 0) and Slot 2 (Index 2) are used, we need coverage up to Index 2.
            // Engine expects a "count" that it multiplies by 2. So we pass maxSlotIndex + 1.
            const totalChannels = Engine.calculateTotalChannels(maxSlotIndex + 1);
            for (let i = 0; i < totalChannels; i++) channelData.push(new Int32Array(maxFrames));

            // FIXED (Anchor 1): Prepare Waveform Pairs Array
            const numPairs = totalChannels / 2;
            let wfPairs = new Array(numPairs).fill(null);

            // 3. Process
            for (let i = 0; i < activeFiles.length; i++) {
                const { file, index } = activeFiles[i];
                setProgress(1 + (i / activeFiles.length) * 19, `Processing file ${i+1}...`);
                
                let pcm = await extractPCM(file);
                const srcSR = sampleRates[index];
                const srcBD = bitDepths[index];
                
                if (srcSR !== targetSR) {
                    log(`Resampling slot ${index+1} from ${srcSR} to ${targetSR}`, 'warn');
                    pcm = resamplePCM(pcm, srcSR, targetSR, 2);
                }
                
                if (srcBD === 16 && targetBD === 24) {
                    for(let k=0; k<pcm.length; k++) pcm[k] = pcm[k] << 8;
                } else if (srcBD === 24 && targetBD === 16) {
                    for(let k=0; k<pcm.length; k++) pcm[k] = pcm[k] >> 8;
                }
                
                // FIXED (Anchor 1): Generate Waveform "On-the-fly" (Memory Optimization)
                if (waveformGenerationEnabled && Engine.computeWaveform) {
                     wfPairs[index] = Engine.computeWaveform(pcm, targetSR);
                }
                
                // FIXED (Anchor 1): Pass 'index' (Slot Index) instead of 'i' (Loop Index)
                // This ensures Slot 2 maps to Channel 4/5, not Channel 2/3.
                Engine.assignToChannels(channelData, pcm, index, maxFrames);
            }
            
            // FIXED (Anchor 1): Fill gaps in Waveform pairs with silence
            if (waveformGenerationEnabled && Engine.computeWaveform) {
                const silentPCM = new Int32Array(100); // Tiny silent buffer
                const silentWF = Engine.computeWaveform(silentPCM, targetSR);
                for(let k=0; k<numPairs; k++) {
                    if (!wfPairs[k]) wfPairs[k] = silentWF;
                }
            }

            // 4. Encode
            if (isCancelled) throw new Error("Operation cancelled");
            setProgress(20, 'Initializing Encoder...');

            const flacData = await new Promise(async (resolve, reject) => {
                const chunks = [];
                // args: sample_rate, channels, bps, compression_level, total_samples (0=unknown), block_size (0=default), verify (0=false)
                const encoder = Flac.create_libflac_encoder(targetSR, totalChannels, targetBD, comp, maxFrames, 0, 0);
                if (!encoder) return reject(new Error("Create encoder failed"));

                const status = Flac.init_encoder_stream(encoder, (data) => chunks.push(new Uint8Array(data)));
                if (status !== 0) return reject(new Error("Init encoder failed: " + status));

                // Chunked Encoding Loop
                const CHUNK_SIZE = 48000 * 2; // Process 2 seconds at a time
                let offset = 0;

                try {
                    while (offset < maxFrames) {
                        if (isCancelled) throw new Error("Operation cancelled");
                        
                        const size = Math.min(CHUNK_SIZE, maxFrames - offset);

                        // Create views for this chunk (no copy, fast)
                        // channelData is Array<Int32Array>
                        const chunkBuffers = channelData.map(ch => ch.subarray(offset, offset + size));

                        const rc = Flac.FLAC__stream_encoder_process(encoder, chunkBuffers, size);
                        if (!rc) throw new Error("Encoding failed");

                        offset += size;

                        // Update UI - Map encoding to 20-50%
                        const pct = 20 + (offset / maxFrames) * 30; 
                        setProgress(pct, `Encoding ${Math.round((offset/maxFrames)*100)}%`);

                        // Yield to main thread
                        await new Promise(r => setTimeout(r, 0));
                    }

                    setProgress(49, 'Finalizing FLAC Container...');
                    await new Promise(r => setTimeout(r, 0));

                    Flac.FLAC__stream_encoder_finish(encoder);
                    Flac.FLAC__stream_encoder_delete(encoder);
                    
                    log(`FLAC created`, 'success');
                    resolve(new Blob(chunks, { type: 'audio/flac' }));

                } catch (err) {
                    Flac.FLAC__stream_encoder_delete(encoder);
                    reject(err);
                }
            });
            
            // 5. Create Waveform JSON (Pro Only)
            if (isCancelled) throw new Error("Operation cancelled");
            
            let wfBlob = null;
            
            // FIXED (Anchor 1): Use pre-calculated wfPairs
            if (waveformGenerationEnabled && Engine.computeWaveform && Engine.createWaveformJson) {
                setProgress(50, 'Generating JSON...');
                const wfJson = Engine.createWaveformJson(totalChannels, wfPairs, targetSR);
                const jsonString = JSON.stringify(wfJson);
                wfBlob = new Blob([jsonString], {type: "application/json"});
            } else {
                const reason = waveformReason && waveformReason.length ? waveformReason : 'Pro feature only';
                log('Skipping Waveform Generation (' + reason + ')', 'info');
            }

            // 6. Upload
            if (isCancelled) throw new Error("Operation cancelled");
            setProgress(60, 'Uploading FLAC...');
            
            const $fn = $get('flac-filename');
            let requested = $fn.length ? ($fn.val() || '').toString().trim() : '';
            
            // Foundation for the Playlist/Track Title
            let foundation = stripExt(requested).replace(/-\d+ch$/i, '').trim();
            let baseNoExt = sanitizeBase(stripExt(requested));
            
            if (!baseNoExt) {
                const tokens = commonTitleFromFiles();
                baseNoExt = sanitizeBase(tokens.join('-')) || 'track';
                foundation = tokens.length ? tokens.join(' ') : 'Track';
            }
            
            baseNoExt = baseNoExt.replace(/-\d+ch$/i, '');
            const finalBase = `${baseNoExt}-${totalChannels}ch`;
            const flacFileName = `${finalBase}.flac`;
            const jsonFileName = `${finalBase}.json`;
            
            let jsonUpload = null;
            let waveformMediaId = 0;

            const flacUpload = await uploadFileInChunks(flacData, flacFileName, subdir, 60, 30);
            
            if (wfBlob) {
                setProgress(90, 'Uploading JSON...');
                jsonUpload = await uploadFileInChunks(wfBlob, jsonFileName, subdir, 90, 10);
                if (jsonUpload && Number.isFinite(jsonUpload.media_id)) {
                    waveformMediaId = jsonUpload.media_id;
                }
            }
            
            setProgress(100, 'Done!');
            log(`Upload Complete: ${subdir}/${flacFileName}`, 'success');
            
            const flacMediaId = flacUpload && Number.isFinite(flacUpload.media_id) ? flacUpload.media_id : 0;

            // Update Security Verification Links if present
            const $secBox = $('#thespl-security-verification-box');
            if ($secBox.length) {
                const flacUrl = flacUpload && flacUpload.url ? flacUpload.url : '';
                const streamUrl = flacMediaId ? `${thespl_muxer_vars.home_url}/?thespl_stream=${flacMediaId}` : '';

                if (flacUrl) {
                    $('#thespl-test-link-direct').text(flacUrl);
                    $('#thespl-test-link-direct-btn').attr('href', flacUrl);
                }
                if (streamUrl) {
                    $('#thespl-test-link-stream').text(streamUrl);
                    $('#thespl-test-link-stream-btn').attr('href', streamUrl);
                }
            }
            
            // Helper for Variant Name (Count filled slots)
            // User requested: "1 Variant", "2 Variants", etc.
            // 2 Channels = 1 Variant
            // 4 Channels = 2 Variants
            // 6 Channels = 3 Variants
            // 8 Channels = 4 Variants
            const variantCount = totalChannels / 2;
            const variantName = variantCount === 1 ? "1 Variant" : `${variantCount} Variants`;
            
            const srDisplay = (targetSR / 1000).toFixed(1).replace('.0', ''); // 44100 -> 44.1, 48000 -> 48
            
            const displayTitle = foundation || finalBase.replace(/-\d+ch$/i, '').replace(/-/g, ' ');
            const playlistJson = {
                variants: Array.from({ length: variantCount }, (_, i) => 'Variant ' + (i + 1)),
                tracks: [
                    {
                        title: displayTitle,
                        artist: '',
                        audio_attachment_id: flacMediaId,
                        waveform_attachment_id: waveformMediaId,
                        cover_attachment_id: 0
                    }
                ]
            };

            const flacUrlDisplay = flacUpload && typeof flacUpload.url === 'string' ? flacUpload.url : '';
            const jsonUrlDisplay = jsonUpload && typeof jsonUpload.url === 'string' ? jsonUpload.url : '';
            const wfSkippedText = waveformReason && waveformReason.length ? waveformReason : 'Pro Feature Only';

            let html = `
                <div class="success-box" style="text-align: center; padding: 20px; background: rgba(35, 134, 54, 0.1); border: 1px solid #3fb950; border-radius: 8px;">
                    <h3 style="margin-top:0; color: #3fb950;">✅ Muxing & Upload Complete!</h3>
                    
                    <p style="margin: 10px 0; color: #fff;">The following files have been encoded and uploaded to the server:</p>
                    
                    <div style="text-align: center; margin: 15px auto; font-family: monospace; font-size: 12px; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 4px; display: inline-block; min-width: 60%;">
                        <div style="margin-bottom: 8px;">
                            <strong>FLAC Audio (${variantName}, ${srDisplay} kHz/${targetBD} bit):</strong><br>
                            ${flacUrlDisplay ? `<span style="color: #a7aaad;">${flacUrlDisplay}</span><br>` : ``}
                            <span style="color: #a7aaad;">Media ID: ${flacMediaId || '-'}</span>
                        </div>
                        
                        <div>
                            ${wfBlob ? 
                                `<strong>JSON Waveform (${variantName}):</strong><br>${jsonUrlDisplay ? `<span style="color: #a7aaad;">${jsonUrlDisplay}</span><br>` : ``}<span style="color: #a7aaad;">Media ID: ${waveformMediaId || '-'}</span>` 
                                : 
                                `<strong style="color: #dba617;">JSON Waveform:</strong> <span style="color: #dba617;">Skipped (${wfSkippedText})</span>`
                            }
                        </div>
                    </div>

                    
                </div>
            `;
            
            $get('result').html(html);

            const $box = $get('result').find('.success-box');
            const ui = `
                <div id="thespl-playlist-actions" style="margin-top: 16px; text-align:left;">
                    <div class="thespl-existing" style="display:flex; gap:8px; align-items:center; justify-content:flex-end;">
                        <select id="thespl-existing-select" style="flex:1; padding:8px; border:1px solid #30363d; background:#0d1117; color:#c9d1d9; border-radius:6px;">
                            <option value="">Select a playlist…</option>
                        </select>
                        <button type="button" class="button" id="thespl-append-existing">Add track to existing playlist</button>
                    </div>
                    <div class="thespl-new" style="display:flex; gap:8px; align-items:center; justify-content:flex-end; margin-top:12px;">
                        <button type="button" class="button button-primary" id="thespl-create-edit">Add track to new playlist</button>
                    </div>
                    <div id="thespl-playlist-feedback" style="margin-top:10px; color:#a7aaad; text-align:right;"></div>
                </div>`;
            $box.append(ui);

            const $select = $box.find('#thespl-existing-select');
            const $feedback = $box.find('#thespl-playlist-feedback');
            const t = (window.thesplMuxerI18n||{});

            const loadPlaylists = function(){
                $.ajax({
                    url: thespl_muxer_vars.ajax_url,
                    type: 'POST',
                    data: { action: 'thespl_search_playlists', nonce: thespl_muxer_vars.nonce, term: '', page: 1, per_page: 10 },
                    success: function(res){
                        if (res && res.success && res.data && Array.isArray(res.data.items)) {
                            $select.empty().append('<option value="">'+(t.selectPlaylist||'Select a playlist…')+'</option>');
                            res.data.items.forEach(function(it){
                                const label = `${it.title} (#${it.id})`;
                                $select.append(`<option value="${it.id}">${label}</option>`);
                            });
                        } else {
                            $feedback.html('<span style="color:#ff7b72;">'+(t.failedLoadPlaylists||'Failed to load playlists')+'</span>');
                        }
                    },
                    error: function(){
                        $feedback.html('<span style="color:#ff7b72;">'+(t.networkError||'Network error')+'</span>');
                    }
                });
            };

            $box.find('#thespl-append-existing').on('click', function(){
                const pid = parseInt($select.val());
                if (!Number.isFinite(pid) || pid <= 0) {
                    $feedback.html('<span style="color:#ff7b72;">'+(t.pleaseSelectPlaylist||'Please select a playlist')+'</span>');
                    return;
                }
                $feedback.text(t.sending||'Sending…');
                $.ajax({
                    url: thespl_muxer_vars.ajax_url,
                    type: 'POST',
                    data: {
                        action: 'thespl_append_track_to_playlist',
                        nonce: thespl_muxer_vars.nonce,
                        playlist_id: pid,
                        title: displayTitle,
                        audio_attachment_id: flacMediaId,
                        waveform_attachment_id: waveformMediaId
                    },
                    success: function(res){
                        if (res && res.success) {
                            const base = (thespl_muxer_vars && thespl_muxer_vars.admin_post_url) ? thespl_muxer_vars.admin_post_url : 'post.php';
                            window.location.href = `${base}?post=${pid}&action=edit`;
                        } else {
                            const msg = res && res.data && res.data.message ? res.data.message : (t.appendError||'Append error');
                            $feedback.html('<span style="color:#ff7b72;">'+msg+'</span>');
                        }
                    },
                    error: function(){
                        $feedback.html('<span style="color:#ff7b72;">'+(t.networkError||'Network error')+'</span>');
                    }
                });
            });

            $box.find('#thespl-create-edit').on('click', function(){
                $feedback.text(t.creatingPlaylist||'Creating playlist…');
                $.ajax({
                    url: thespl_muxer_vars.ajax_url,
                    type: 'POST',
                    data: {
                        action: 'thespl_create_playlist_from_upload',
                        nonce: thespl_muxer_vars.nonce,
                        title: displayTitle,
                        playlist_json: JSON.stringify(playlistJson)
                    },
                    success: function(res){
                        if (res && res.success && res.data && res.data.playlist_id) {
                            const pid = parseInt(res.data.playlist_id);
                            const base = (thespl_muxer_vars && thespl_muxer_vars.admin_post_url) ? thespl_muxer_vars.admin_post_url : 'post.php';
                            window.location.href = `${base}?post=${pid}&action=edit`;
                        } else {
                            const msg = res && res.data && res.data.message ? res.data.message : (t.createError||'Create error');
                            $feedback.html('<span style="color:#ff7b72;">'+msg+'</span>');
                        }
                    },
                    error: function(){
                        $feedback.html('<span style="color:#ff7b72;">'+(t.networkError||'Network error')+'</span>');
                    }
                });
            });

            loadPlaylists();


        } catch (e) {
            log(`Operation failed: ${e.message}`, 'error');
            setProgress(0, 'Failed');
            $get('status').text('Error: ' + e.message);
            $container.find('.status-display').addClass('status-error');
        } finally {
            isConverting = false;
            setUnloadGuard(false);
            $get('run').prop('disabled', false);
            $get('reset').text('Reset All').css('background-color', '').css('border-color', '').css('color', '');
            updateButtonState();
        }
    });

    // --- Init ---
    const checkEngineReady = () => {
        if (typeof Flac === 'undefined') return false;

        // Robust check for different libflac versions (snake_case vs camelCase, method vs property)
        let isReady = false;
        if (typeof Flac.is_ready === 'function') isReady = Flac.is_ready();
        else if (typeof Flac.isReady === 'function') isReady = Flac.isReady();
        else if (Flac.isReady === true) isReady = true;

        if (isReady) {
            if (!libflacReady) {
                libflacReady = true;
                log('FLAC Encoder Ready', 'success');
                updateButtonState();
            }
            return true;
        }
        return false;
    };

    if (!checkEngineReady()) {
        log('Loading Audio Engine...', 'info');
        
        // Listen for event if Flac exists
        if (typeof Flac !== 'undefined') {
            if (typeof Flac.on === 'function') {
                Flac.on('ready', checkEngineReady);
            } else if (typeof Flac.onready !== 'undefined') {
                // Fallback for some versions
                const old = Flac.onready;
                Flac.onready = () => { if(old) old(); checkEngineReady(); };
            }
        }

        // Poll as backup (covers undefined or missed event)
        const checkFlac = setInterval(function() {
            if (checkEngineReady()) {
                clearInterval(checkFlac);
            }
        }, 250);
    }

    // Initial Render
    for (let i = 0; i < MAX_SLOTS; i++) {
        renderFileSlot(i);
    }
    updateStatus();
    adjustFilenameFieldWidth();
    updateRadioVisualSelection();
});
