(function (wp) {
    const { registerPlugin } = wp.plugins;
    // Handle deprecation: PluginSidebar moved to wp.editor in newer versions
    const PluginSidebar = (wp.editor && wp.editor.PluginSidebar) ? wp.editor.PluginSidebar : wp.editPost.PluginSidebar;
    // wp.richText not used (emotion tags removed)
    const { __, sprintf } = wp.i18n;
    const { PanelRow, Button, SelectControl, Spinner, PanelBody, Modal } = wp.components;
    const { withSelect, withDispatch } = wp.data;
    const { compose } = wp.compose;
    const { createElement, Component, Fragment, useState, useEffect, useCallback, useRef } = wp.element;
    const { addQueryArgs } = wp.url;
    const { apiFetch } = wp;
    // V-09: console.log をプロダクションコードから削除

    // V-06: HTMLタグと危険な属性を正規表現で除去しプレーンテキストに変換するヘルパー
    function stripHtmlTags(html) {
        if (!html) return '';
        return String(html).replace(/<[^>]*>/g, ' ').replace(/\s{2,}/g, ' ').trim();
    }

    function getPlainTextFromBlockContent(content) {
        if (!content) {
            return '';
        }

        return String(content)
            .replace(/<br\s*\/?>/gi, '\n')
            .replace(/<\/p>\s*<p>/gi, '\n\n')
            .replace(/<[^>]*>/g, ' ')
            .replace(/\r\n|\r/g, '\n')
            .replace(/[ \t]+/g, ' ')
            .replace(/\n{3,}/g, '\n\n')
            .trim();
    }

    function findVoiceById(voiceList, voiceId) {
        if (!Array.isArray(voiceList) || !voiceId) {
            return null;
        }

        return voiceList.find((voice) => voice.voice_id === voiceId) || null;
    }

    function getVoiceGenderById(voiceList, voiceId) {
        const matchedVoice = findVoiceById(voiceList, voiceId);

        return matchedVoice && matchedVoice.gender ? matchedVoice.gender : '';
    }

    function normalizeScriptMode(scriptMode, isPro) {
        if (scriptMode === 'multi') {
            return isPro ? 'multi' : 'single';
        }

        return 'single';
    }

    function getDefaultSmartToneStyleId(locData, scriptMode) {
        const smartToneDefaults = locData && locData.smartToneDefaults ? locData.smartToneDefaults : {};
        if (scriptMode === 'multi' && smartToneDefaults.multi) {
            return smartToneDefaults.multi;
        }

        return smartToneDefaults.single || '';
    }

    function getDefaultPromptSetId(locData, scriptMode) {
        const promptSetDefaults = locData && locData.promptSetDefaults ? locData.promptSetDefaults : {};
        if (scriptMode === 'multi' && promptSetDefaults.multi) {
            return promptSetDefaults.multi;
        }

        return promptSetDefaults.single || '';
    }

    function getDefaultPreviewLanguage(siteLocale) {
        const normalizedLocale = String(siteLocale || '').trim().toLowerCase();

        if (normalizedLocale.startsWith('ja')) {
            return 'ja';
        }

        return 'en';
    }

    const LANGUAGE_LABELS = {
        ja: __('Japanese', 'quikvox-ai'),
        en: __('English', 'quikvox-ai'),
        'zh-cn': __('Chinese (Simplified)', 'quikvox-ai'),
        'zh-tw': __('Chinese (Traditional)', 'quikvox-ai'),
        ko: __('Korean', 'quikvox-ai'),
    };

    const LENGTH_OPTIONS = [
        { value: '30sec', label: __('30 sec', 'quikvox-ai') },
        { value: '60sec', label: __('60 sec', 'quikvox-ai') },
        { value: '180sec', label: __('180 sec', 'quikvox-ai') },
    ];

    function normalizeLanguageCode(languageCode) {
        return String(languageCode || '').trim().toLowerCase();
    }

    function getLanguageDisplayLabel(languageCode) {
        const normalizedLanguageCode = normalizeLanguageCode(languageCode);

        if (!normalizedLanguageCode) {
            return __('English', 'quikvox-ai');
        }

        return LANGUAGE_LABELS[normalizedLanguageCode] || normalizedLanguageCode.toUpperCase();
    }

    function normalizeLengthChoice(lengthChoice) {
        const normalizedLengthChoice = String(lengthChoice || '').trim().toLowerCase();
        const matchedLengthChoice = LENGTH_OPTIONS.find((option) => option.value === normalizedLengthChoice);

        return matchedLengthChoice ? matchedLengthChoice.value : '30sec';
    }

    function getPromptSetSpeakerType(promptSet) {
        return promptSet && promptSet.speaker_type === 'multi' ? 'multi' : 'single';
    }

    function getPromptSetLanguage(promptSet) {
        if (!promptSet || !promptSet.language) {
            return 'en';
        }

        return normalizeLanguageCode(promptSet.language) || 'en';
    }

    function buildPromptSetEntries(promptSetLookup) {
        if (!promptSetLookup || typeof promptSetLookup !== 'object') {
            return [];
        }

        return Object.entries(promptSetLookup).map(([promptId, promptData]) => ({
            value: promptId,
            label: promptData && promptData.name ? String(promptData.name) : promptId,
            name: promptData && promptData.name ? String(promptData.name) : promptId,
            language: getPromptSetLanguage(promptData),
            speaker_type: getPromptSetSpeakerType(promptData),
            source_type: promptData && promptData.source_type ? String(promptData.source_type) : 'custom',
        }));
    }

    function buildLanguageOptionsFromPromptSets(promptSetEntries, promptSetLookup) {
        const orderedCodes = [];
        const seenCodes = new Set();
        const sourceEntries = Array.isArray(promptSetEntries) && promptSetEntries.length > 0
            ? promptSetEntries
            : buildPromptSetEntries(promptSetLookup);

        sourceEntries.forEach((entry) => {
            const languageCode = normalizeLanguageCode(entry && entry.language ? entry.language : '');
            if (!languageCode || seenCodes.has(languageCode)) {
                return;
            }

            seenCodes.add(languageCode);
            orderedCodes.push(languageCode);
        });

        return orderedCodes.map((languageCode) => ({
            value: languageCode,
            label: getLanguageDisplayLabel(languageCode),
        }));
    }

    function buildPromptSetOptionsForContext(promptSetEntries, promptSetLookup, languageCode, scriptMode) {
        const normalizedLanguageCode = normalizeLanguageCode(languageCode);
        const normalizedScriptMode = scriptMode === 'multi' ? 'multi' : 'single';
        const sourceEntries = Array.isArray(promptSetEntries) && promptSetEntries.length > 0
            ? promptSetEntries
            : buildPromptSetEntries(promptSetLookup);

        return sourceEntries.filter((entry) => {
            const entryLanguageCode = normalizeLanguageCode(entry && entry.language ? entry.language : '');
            const entryScriptMode = getPromptSetSpeakerType(entry);

            return entryLanguageCode === normalizedLanguageCode && entryScriptMode === normalizedScriptMode;
        });
    }

    function resolvePromptSetIdForContext(promptSetEntries, promptSetLookup, languageCode, scriptMode, preferredPromptId) {
        const filteredPromptSets = buildPromptSetOptionsForContext(
            promptSetEntries,
            promptSetLookup,
            languageCode,
            scriptMode
        );

        if (!filteredPromptSets.length) {
            return '';
        }

        if (preferredPromptId) {
            const preferredEntry = filteredPromptSets.find((entry) => entry.value === preferredPromptId);
            if (preferredEntry) {
                return preferredEntry.value;
            }
        }

        return filteredPromptSets[0].value;
    }

    function getFilteredSmartToneStyles(styleOptions, scriptMode) {
        if (!Array.isArray(styleOptions)) {
            return [];
        }

        const normalizedMode = scriptMode === 'multi' ? 'multi' : 'single';
        const filteredOptions = styleOptions.filter((option) => {
            const speakerType = option && option.speaker_type === 'multi' ? 'multi' : 'single';
            return speakerType === normalizedMode;
        });

        return filteredOptions.length > 0 ? filteredOptions : styleOptions;
    }

    /**
     * @typedef {Object} GenerationEstimate
     * @property {string} title
     * @property {string} rangeLabel
     * @property {string} detail
     * @property {number} totalSeconds
     * @property {boolean} isChunkBased
     */

    /**
     * Clamp a number into a safe range.
     *
     * @param {number} value
     * @param {number} minValue
     * @param {number} maxValue
     * @returns {number}
     */
    function clampNumber(value, minValue, maxValue) {
        return Math.min(maxValue, Math.max(minValue, value));
    }

    /**
     * Format the remaining time label used in the compact popup.
     *
     * @param {number} remainingSeconds
     * @returns {string}
     */
    function formatGenerationRemainingLabel(remainingSeconds) {
        const safeRemainingSeconds = Math.max(0, Math.round(Number(remainingSeconds) || 0));

        if (safeRemainingSeconds <= 0) {
            return __('Complete', 'quikvox-ai');
        }

        if (safeRemainingSeconds < 30) {
            return __('Less than 30 sec left', 'quikvox-ai');
        }

        if (safeRemainingSeconds < 60) {
            return __('About 1 min left', 'quikvox-ai');
        }

        const remainingMinutes = Math.max(1, Math.round(safeRemainingSeconds / 60));
        return sprintf(__('About %d min left', 'quikvox-ai'), remainingMinutes);
    }

    /**
     * Build a compact estimate for the generation popup.
     *
     * @param {string} operationKey
     * @param {Object} options
     * @param {number} [options.textLength]
     * @param {number} [options.chunkCount]
     * @returns {GenerationEstimate}
     */
    function buildGenerationEstimate(operationKey, options) {
        const safeTextLength = Math.max(0, Number(options && options.textLength) || 0);
        const safeChunkCount = Math.max(0, Number(options && options.chunkCount) || 0);

        if (operationKey === 'apply-smart-tone') {
            if (safeTextLength > 5000) {
                return {
                    title: __('Apply Smart Tone', 'quikvox-ai'),
                    rangeLabel: __('30-60 sec', 'quikvox-ai'),
                    detail: __('Styling a longer script usually takes a little longer, so we keep the range a bit wider.', 'quikvox-ai'),
                    totalSeconds: 48,
                    isChunkBased: false,
                };
            }

            if (safeTextLength > 2500) {
                return {
                    title: __('Apply Smart Tone', 'quikvox-ai'),
                    rangeLabel: __('20-45 sec', 'quikvox-ai'),
                    detail: __('This is a moderate Smart Tone pass, so the popup uses a narrower estimate.', 'quikvox-ai'),
                    totalSeconds: 32,
                    isChunkBased: false,
                };
            }

            return {
                title: __('Apply Smart Tone', 'quikvox-ai'),
                rangeLabel: __('15-35 sec', 'quikvox-ai'),
                detail: __('Smart Tone is usually lighter than script generation, so the estimate is shorter.', 'quikvox-ai'),
                totalSeconds: 24,
                isChunkBased: false,
            };
        }

        if (operationKey === 'generate-voice') {
            if (safeChunkCount > 0) {
                const lowerBound = Math.max(60, safeChunkCount * 12);
                const upperBound = Math.max(lowerBound + 45, safeChunkCount * 24);
                return {
                    title: __('Generate Voice', 'quikvox-ai'),
                    rangeLabel: sprintf(__('%1$d-%2$d min', 'quikvox-ai'), Math.round(lowerBound / 60), Math.round(upperBound / 60)),
                    detail: sprintf(__('%d chunk%s are processed one by one, so we show chunk progress and a rough range.', 'quikvox-ai'), safeChunkCount, safeChunkCount === 1 ? '' : 's'),
                    totalSeconds: Math.round((lowerBound + upperBound) / 2),
                    isChunkBased: true,
                };
            }

            return {
                title: __('Generate Voice', 'quikvox-ai'),
                rangeLabel: __('1-3 min', 'quikvox-ai'),
                detail: __('Single-speaker generation is still request based, so the popup shows a broad range.', 'quikvox-ai'),
                totalSeconds: 120,
                isChunkBased: false,
            };
        }

        if (safeTextLength > 5000) {
            return {
                title: __('Generate Script', 'quikvox-ai'),
                rangeLabel: __('90-180 sec', 'quikvox-ai'),
                detail: __('Long source articles usually need more time for the model to draft the script.', 'quikvox-ai'),
                totalSeconds: 120,
                isChunkBased: false,
            };
        }

        if (safeTextLength > 2500) {
            return {
                title: __('Generate Script', 'quikvox-ai'),
                rangeLabel: __('60-120 sec', 'quikvox-ai'),
                detail: __('A medium-sized source article usually lands in a one-to-two minute window.', 'quikvox-ai'),
                totalSeconds: 82,
                isChunkBased: false,
            };
        }

        return {
            title: __('Generate Script', 'quikvox-ai'),
            rangeLabel: __('45-90 sec', 'quikvox-ai'),
            detail: __('The script popup uses a historical range rather than a hard ETA so it stays honest.', 'quikvox-ai'),
            totalSeconds: 68,
            isChunkBased: false,
        };
    }

    /**
     * Derive the phase label from a compact progress percentage.
     *
     * @param {number} progressPercent
     * @returns {string}
     */
    function getGenerationPhaseLabel(progressPercent) {
        if (progressPercent >= 100) {
            return __('Done', 'quikvox-ai');
        }

        if (progressPercent >= 85) {
            return __('Finalizing', 'quikvox-ai');
        }

        if (progressPercent >= 45) {
            return __('Processing', 'quikvox-ai');
        }

        if (progressPercent >= 10) {
            return __('Queued', 'quikvox-ai');
        }

        return __('Starting', 'quikvox-ai');
    }

    /**
     * Estimate the average time needed for one voice chunk.
     *
     * @param {number} textLength
     * @param {number} totalChunks
     * @returns {number}
     */
    function getEstimatedVoiceChunkSeconds(textLength, totalChunks) {
        const safeTextLength = Math.max(0, Number(textLength) || 0);
        const safeTotalChunks = Math.max(1, Number(totalChunks) || 1);
        const averageChunkTextLength = safeTextLength / safeTotalChunks;

        return clampNumber(Math.round(24 + (averageChunkTextLength / 180)), 28, 55);
    }

    /**
     * Estimate the audio finalize time.
     *
     * @param {number} totalChunks
     * @returns {number}
     */
    function getEstimatedVoiceFinalizeSeconds(totalChunks) {
        const safeTotalChunks = Math.max(1, Number(totalChunks) || 1);
        return clampNumber(6 + (safeTotalChunks * 2), 8, 18);
    }

    function getActiveVoiceDirectionTemplate(styleOptions, scriptMode, selectedToneStyle) {
        const filteredToneStyles = getFilteredSmartToneStyles(styleOptions, scriptMode);
        const activeStyle = filteredToneStyles.find((option) => option.value === selectedToneStyle)
            || filteredToneStyles[0]
            || null;

        if (!activeStyle || !activeStyle.voice_direction_template) {
            return '';
        }

        return String(activeStyle.voice_direction_template).trim();
    }

    function parseSpeakerMapJson(rawValue) {
        if (!rawValue) {
            return {};
        }

        if (typeof rawValue === 'object') {
            return rawValue;
        }

        try {
            const decoded = JSON.parse(String(rawValue));
            return decoded && typeof decoded === 'object' ? decoded : {};
        } catch (error) {
            return {};
        }
    }

    function getScriptPlainTextFromBlocks(blocks) {
        if (!Array.isArray(blocks)) {
            return '';
        }

        return blocks
            .filter((block) => block && block.name === 'core/paragraph')
            .map((block) => getPlainTextFromBlockContent(block.attributes && block.attributes.content ? block.attributes.content : ''))
            .map((paragraphText) => paragraphText
                .split('\n')
                .map((line) => normalizeSpeakerTaggedLine(line))
                .join('\n'))
            .filter((text) => text)
            .join('\n\n')
            .trim();
    }

    function normalizeSpeakerTaggedLine(text) {
        const normalizedText = String(text || '').trim();
        if (!normalizedText) {
            return '';
        }

        const speakerMatch = normalizedText.match(/^([^:\n：]{1,80}?)(?:\s*((?:\[[^\]\n]+\]\s*)+))?\s*[:：]\s*(.*)$/s);
        if (!speakerMatch) {
            return normalizedText;
        }

        const speakerName = String(speakerMatch[1] || '').trim();
        const inlineTagBlock = String(speakerMatch[2] || '').trim();
        const dialogueBody = String(speakerMatch[3] || '').trim();
        const speakerTags = (inlineTagBlock.match(/\[[^\]\n]+\]/g) || [])
            .concat(dialogueBody.match(/\[[^\]\n]+\]/g) || []);
        const normalizedDialogueBody = dialogueBody
            .replace(/\[[^\]\n]+\]/g, '')
            .replace(/\s{2,}/g, ' ')
            .trim();
        const tagPrefix = speakerTags.length > 0 ? `${speakerTags.join(' ')} ` : '';

        return `${speakerName}: ${tagPrefix}${normalizedDialogueBody}`.trim();
    }

    function normalizeSpeakerNameKey(text) {
        return String(text || '').trim().toLowerCase();
    }

    function extractSpeakerLine(text) {
        const normalizedText = normalizeSpeakerTaggedLine(text);
        const match = normalizedText.match(/^([^:\n：]{1,80})\s*[:：]\s*(.*)$/s);
        if (!match) {
            return null;
        }

        const speakerName = match[1].trim();
        const dialogueText = match[2].trim();
        const speakerKey = normalizeSpeakerNameKey(speakerName);

        return {
            speaker: speakerName,
            speakerKey,
            text: dialogueText,
            label: `${speakerName}:`,
        };
    }

    function parseDialogueTurnsFromText(text) {
        const normalizedText = String(text || '').replace(/\r\n|\r/g, '\n').trim();
        if (!normalizedText) {
            return [];
        }

        const turns = [];
        let currentTurn = null;
        let hasSpeakerLine = false;

        normalizedText
            .split('\n')
            .map((line) => normalizeSpeakerTaggedLine(line))
            .map((line) => String(line || '').trim())
            .filter((line) => line)
            .forEach((line) => {
                const speakerLine = extractSpeakerLine(line);
                if (speakerLine) {
                    hasSpeakerLine = true;
                    if (currentTurn) {
                        turns.push(currentTurn);
                    }
                    currentTurn = {
                        speaker: speakerLine.speaker,
                        speakerKey: speakerLine.speakerKey,
                        text: speakerLine.text,
                        label: speakerLine.label,
                    };
                    return;
                }

                if (!currentTurn) {
                    return;
                }

                currentTurn.text = `${currentTurn.text}\n${line}`.trim();
            });

        if (currentTurn) {
            turns.push(currentTurn);
        }

        if (!hasSpeakerLine) {
            return [];
        }

        return turns.filter((turn) => turn && turn.speaker && turn.text);
    }

    function analyzeSpeakerBlocks(blocks) {
        const blockSpeakerInfo = {};
        const detectedSpeakers = [];
        const detectedSpeakerKeys = [];
        let unlabeledParagraphCount = 0;
        let emptyDialogueCount = 0;
        let mixedSpeakerBlockCount = 0;

        if (!Array.isArray(blocks)) {
            return {
                blockSpeakerInfo,
                detectedSpeakers,
                unlabeledParagraphCount,
                emptyDialogueCount,
                mixedSpeakerBlockCount,
            };
        }

        blocks.forEach((block) => {
            if (!block || block.name !== 'core/paragraph') {
                return;
            }

            const plainText = getPlainTextFromBlockContent(block.attributes && block.attributes.content ? block.attributes.content : '').trim();
            if (!plainText) {
                return;
            }

            const detectedLines = parseDialogueTurnsFromText(plainText);

            if (detectedLines.length === 0) {
                unlabeledParagraphCount += 1;
                return;
            }

            if (detectedLines.length > 1) {
                mixedSpeakerBlockCount += 1;
            }

            detectedLines.forEach((speakerLine) => {
                if (!speakerLine.text) {
                    emptyDialogueCount += 1;
                }

                if (!detectedSpeakerKeys.includes(speakerLine.speakerKey)) {
                    detectedSpeakerKeys.push(speakerLine.speakerKey);
                    detectedSpeakers.push(speakerLine.speaker);
                }
            });

            const firstSpeakerLine = detectedLines[0];

            blockSpeakerInfo[block.clientId] = {
                speaker: firstSpeakerLine.speaker,
                speakerKey: firstSpeakerLine.speakerKey,
                order: detectedSpeakerKeys.indexOf(firstSpeakerLine.speakerKey),
                label: firstSpeakerLine.label,
            };
        });

        return {
            blockSpeakerInfo,
            detectedSpeakers,
            unlabeledParagraphCount,
            emptyDialogueCount,
            mixedSpeakerBlockCount,
        };
    }

    function buildSpeakerAwareParagraphs(lines) {
        const paragraphs = [];
        let currentParagraph = '';

        lines.forEach((line) => {
            const speakerLine = extractSpeakerLine(line);
            if (speakerLine) {
                if (currentParagraph) {
                    paragraphs.push(currentParagraph);
                }
                currentParagraph = line;
                return;
            }

            if (!currentParagraph) {
                currentParagraph = line;
                return;
            }

            currentParagraph += `\n${line}`;
        });

        if (currentParagraph) {
            paragraphs.push(currentParagraph);
        }

        return paragraphs;
    }

    function buildScriptParagraphs(scriptText, scriptMode) {
        const normalizedScriptText = String(scriptText || '').replace(/\r\n|\r/g, '\n').trim();
        if (!normalizedScriptText) {
            return [];
        }

        if (scriptMode !== 'multi') {
            const lines = normalizedScriptText
                .split('\n')
                .map((line) => normalizeSpeakerTaggedLine(line))
                .filter((line) => line);
            const hasSpeakerTaggedLine = lines.some((line) => !!extractSpeakerLine(line));

            if (hasSpeakerTaggedLine) {
                return buildSpeakerAwareParagraphs(lines);
            }

            return normalizedScriptText
                .split(/\n{2,}/)
                .map((paragraph) => paragraph.trim())
                .filter((paragraph) => paragraph);
        }

        const parsedTurns = parseDialogueTurnsFromText(normalizedScriptText);
        if (parsedTurns.length > 0) {
            return parsedTurns.map((turn) => normalizeSpeakerTaggedLine(`${turn.speaker}: ${turn.text}`));
        }

        const lines = normalizedScriptText
            .split('\n')
            .map((line) => normalizeSpeakerTaggedLine(line))
            .filter((line) => line);
        return buildSpeakerAwareParagraphs(lines);
    }

    function splitScriptPasteIntoParagraphTexts(scriptText, scriptMode) {
        const paragraphs = buildScriptParagraphs(scriptText, scriptMode);
        return Array.isArray(paragraphs)
            ? paragraphs.map((paragraph) => String(paragraph || '').trim()).filter((paragraph) => !!paragraph)
            : [];
    }

    function getPasteBlockClientIdFromTarget(target) {
        if (!target || !target.closest) {
            return '';
        }

        const blockElement = target.closest('[data-block]');
        if (!blockElement) {
            return '';
        }

        const blockClientId = blockElement.getAttribute('data-block');
        return typeof blockClientId === 'string' ? blockClientId.trim() : '';
    }

    function buildVoiceSummaryLabel(scriptMode, selectedVoice, speakerMap, detectedSpeakers) {
        if (scriptMode !== 'multi') {
            return selectedVoice ? sprintf(__('Speaker 1 %s', 'quikvox-ai'), selectedVoice) : '';
        }

        return detectedSpeakers
            .filter((speakerName) => !!speakerMap[normalizeSpeakerNameKey(speakerName)])
            .map((speakerName, index) => `Speaker ${index + 1} ${speakerMap[normalizeSpeakerNameKey(speakerName)]}`)
            .join(' / ');
    }

    function normalizeVoiceSummaryLabel(summaryLabel) {
        if (!summaryLabel) {
            return '';
        }

        return String(summaryLabel)
            .replace(/Speaker\s*(\d+)\s*:\s*/gi, 'Speaker $1 ')
            .replace(/\s*\/\s*/g, ' / ')
            .replace(/\s+/g, ' ')
            .trim();
    }

    function buildTtsModelOptions(ttsModels, selectedTtsModel) {
        const sourceModels = Array.isArray(ttsModels) ? ttsModels : [];
        const mappedOptions = sourceModels.map((model) => {
            if (typeof model === 'string') {
                return { label: model, value: model };
            }

            const value = model && (model.value || model.id || model.name || model.model_id);
            const label = model && (model.label || model.name || model.id || model.value || model.model_id);
            return {
                label: label ? String(label) : String(value || ''),
                value: value ? String(value) : String(label || ''),
            };
        }).filter((option) => !!option.value);

        const currentValue = String(selectedTtsModel || '').trim();

        return mappedOptions.length > 0 ? mappedOptions : [{
            label: currentValue || __('No TTS model available', 'quikvox-ai'),
            value: currentValue || '',
        }];
    }

    function resolveAvailableModel(selectedModel, availableModels, fallbackModel) {
        const selectedValue = String(selectedModel || '').trim();
        const fallbackValue = String(fallbackModel || '').trim();
        const normalizedAvailableModels = extractModelValues(availableModels);

        if (selectedValue && normalizedAvailableModels.includes(selectedValue)) {
            return selectedValue;
        }

        if (normalizedAvailableModels.length > 0) {
            return normalizedAvailableModels[0];
        }

        return fallbackValue;
    }

    function extractModelValues(models) {
        if (!Array.isArray(models)) {
            return [];
        }

        return models.map((model) => {
            if (typeof model === 'string') {
                return model.trim();
            }

            const value = model && (model.value || model.id || model.name || model.model_id);
            return String(value || '').trim();
        }).filter(Boolean);
    }

    function getEditorCanvasDocuments() {
        const documents = [document];
        const iframeNodes = document.querySelectorAll('iframe[name="editor-canvas"], iframe.block-editor-iframe__iframe');

        iframeNodes.forEach((iframeNode) => {
            try {
                if (iframeNode && iframeNode.contentDocument) {
                    documents.push(iframeNode.contentDocument);
                }
            } catch (error) {
                // Ignore inaccessible iframe documents.
            }
        });

        return documents;
    }

    function ensureVoiceDirectionMount() {
        const contentNode = document.querySelector('.interface-interface-skeleton__content');
        if (!contentNode) {
            return null;
        }

        let mountNode = contentNode.querySelector('.podcastmaker-voice-direction-mount');
        if (!mountNode) {
            mountNode = document.createElement('div');
            mountNode.className = 'podcastmaker-voice-direction-mount';
            contentNode.insertBefore(mountNode, contentNode.firstChild);
        }

        return mountNode;
    }

    function buildDialogueTurnsPreview(blocks, scriptMode) {
        if (!Array.isArray(blocks)) {
            return [];
        }

        const paragraphs = blocks
            .filter((block) => block && block.name === 'core/paragraph')
            .map((block) => getPlainTextFromBlockContent(block.attributes && block.attributes.content ? block.attributes.content : '').trim())
            .filter((paragraph) => paragraph);

        const normalizedScriptMode = scriptMode === 'multi' ? 'multi' : 'single';

        if (normalizedScriptMode === 'multi') {
            const dialogueTurns = [];
            paragraphs.forEach((paragraph) => {
                const parsedTurns = parseDialogueTurnsFromText(paragraph);
                if (parsedTurns.length === 0) {
                    dialogueTurns.push({
                        turn: dialogueTurns.length + 1,
                        speaker: 'Unknown',
                        text: paragraph,
                    });
                    return;
                }

                parsedTurns.forEach((parsedTurn) => {
                    dialogueTurns.push({
                        turn: dialogueTurns.length + 1,
                        speaker: parsedTurn.speaker,
                        text: parsedTurn.text,
                    });
                });
            });

            return dialogueTurns;
        }

        return paragraphs.map((paragraph, index) => {
            const speakerLine = extractSpeakerLine(paragraph);
            const dialogueTurn = {
                turn: index + 1,
                text: speakerLine ? speakerLine.text : paragraph,
            };

            return dialogueTurn;
        });
    }

    function countUtf8Bytes(text) {
        return new TextEncoder().encode(String(text || '')).length;
    }

    function buildMultiSpeakerPromptPreview(turns, voiceDirection) {
        const speakerNames = [];
        const lines = [];

        turns.forEach((turn) => {
            const speakerName = turn && turn.speaker ? String(turn.speaker).trim() : '';
            const turnText = turn && turn.text ? String(turn.text).trim() : '';
            if (!speakerName || !turnText) {
                return;
            }

            if (!speakerNames.includes(speakerName)) {
                speakerNames.push(speakerName);
            }
            lines.push(`${speakerName}: ${turnText}`);
        });

        const normalizedVoiceDirection = String(voiceDirection || '').trim();
        const voiceDirectionBlock = normalizedVoiceDirection
            ? `Voice Direction:\n${normalizedVoiceDirection}\n\n`
            : '';

        return `${voiceDirectionBlock}TTS the following conversation between ${speakerNames.join(' and ')}:\n${lines.join('\n')}`;
    }

    function buildSingleSpeakerPromptPreview(text, voiceDirection) {
        const normalizedText = String(text || '').trim();
        const normalizedVoiceDirection = String(voiceDirection || '').trim();

        if (!normalizedVoiceDirection) {
            return normalizedText;
        }

        return `Voice Direction:\n${normalizedVoiceDirection}\n\nTTS the following script in a single voice:\n${normalizedText}`;
    }

    function getSpeakerMapValue(speakerMap, speakerName) {
        const normalizedMap = speakerMap && typeof speakerMap === 'object' ? speakerMap : {};
        const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
        if (!normalizedSpeakerName) {
            return '';
        }

        const matchingKey = Object.keys(normalizedMap).find((key) => normalizeSpeakerNameKey(key) === normalizedSpeakerName);
        if (!matchingKey) {
            return '';
        }

        return String(normalizedMap[matchingKey] || '').trim();
    }

    function buildSpeakerVoiceConfigsPreview(turns, speakerMap) {
        const normalizedTurns = Array.isArray(turns) ? turns : [];
        const seenSpeakers = [];
        const speakerConfigs = [];

        normalizedTurns.forEach((turn) => {
            const speakerName = String(turn && turn.speaker ? turn.speaker : '').trim();
            if (!speakerName) {
                return;
            }

            const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
            if (!normalizedSpeakerName || seenSpeakers.includes(normalizedSpeakerName)) {
                return;
            }

            const voiceName = getSpeakerMapValue(speakerMap, speakerName);
            if (!voiceName) {
                return;
            }

            seenSpeakers.push(normalizedSpeakerName);
            speakerConfigs.push({
                speaker: speakerName,
                voiceConfig: {
                    prebuiltVoiceConfig: {
                        voiceName,
                    },
                },
            });
        });

        return speakerConfigs;
    }

    function buildSingleSpeakerTtsPayloadPreview(text, selectedVoice, voiceDirection) {
        const promptText = buildSingleSpeakerPromptPreview(text, voiceDirection);

        return {
            contents: [
                {
                    parts: [
                        { text: promptText },
                    ],
                },
            ],
            generationConfig: {
                responseModalities: ['AUDIO'],
                speechConfig: {
                    voiceConfig: {
                        prebuiltVoiceConfig: {
                            voiceName: String(selectedVoice || '').trim(),
                        },
                    },
                },
            },
        };
    }

    function buildMultiSpeakerTtsPayloadPreview(turns, speakerMap, voiceDirection) {
        const promptText = buildMultiSpeakerPromptPreview(turns, voiceDirection);

        return {
            contents: [
                {
                    parts: [
                        { text: promptText },
                    ],
                },
            ],
            generationConfig: {
                responseModalities: ['AUDIO'],
                speechConfig: {
                    multiSpeakerVoiceConfig: {
                        speakerVoiceConfigs: buildSpeakerVoiceConfigsPreview(turns, speakerMap),
                    },
                },
            },
        };
    }

    function splitTurnTextByBytesForPreview(speakerName, turnText, voiceDirection, maxTotalBytes) {
        const previewBudget = Math.max(600, maxTotalBytes - countUtf8Bytes(buildMultiSpeakerPromptPreview([], voiceDirection)) - 32);
        const segments = [];
        let currentSegment = '';

        Array.from(String(turnText || '')).forEach((character) => {
            const candidateSegment = `${currentSegment}${character}`;
            if (countUtf8Bytes(candidateSegment) > previewBudget && currentSegment) {
                segments.push(currentSegment.trim());
                currentSegment = character;
                return;
            }

            currentSegment = candidateSegment;
        });

        if (currentSegment.trim()) {
            segments.push(currentSegment.trim());
        }

        return segments.map((segmentText) => ({
            turn: 0,
            speaker: speakerName,
            text: segmentText,
        }));
    }

    function buildMultiSpeakerChunkPlanForPreview(turns, voiceDirection, maxTotalBytes) {
        const normalizedTurns = Array.isArray(turns) ? turns.filter((turn) => turn && turn.speaker && turn.text) : [];
        const chunks = [];
        let currentChunk = [];

        normalizedTurns.forEach((turn) => {
            const singleTurnPromptBytes = countUtf8Bytes(buildMultiSpeakerPromptPreview([turn], voiceDirection));
            if (singleTurnPromptBytes > maxTotalBytes) {
                if (currentChunk.length > 0) {
                    chunks.push(currentChunk);
                    currentChunk = [];
                }

                splitTurnTextByBytesForPreview(turn.speaker, turn.text, voiceDirection, maxTotalBytes).forEach((splitTurn) => {
                    chunks.push([splitTurn]);
                });
                return;
            }

            const candidateChunk = currentChunk.concat(turn);
            if (countUtf8Bytes(buildMultiSpeakerPromptPreview(candidateChunk, voiceDirection)) > maxTotalBytes) {
                if (currentChunk.length > 0) {
                    chunks.push(currentChunk);
                }
                currentChunk = [turn];
                return;
            }

            currentChunk = candidateChunk;
        });

        if (currentChunk.length > 0) {
            chunks.push(currentChunk);
        }

        return chunks.map((chunkTurns, index) => {
            const turnNumbers = chunkTurns
                .map((turn) => Number(turn.turn) || 0)
                .filter((turnNumber) => turnNumber > 0);

            return {
                chunkIndex: index,
                turnCount: chunkTurns.length,
                turnRange: turnNumbers.length > 0 ? [Math.min(...turnNumbers), Math.max(...turnNumbers)] : [],
                payloadBytes: countUtf8Bytes(buildMultiSpeakerPromptPreview(chunkTurns, voiceDirection)),
                turns: chunkTurns,
            };
        });
    }

    function collectValidationState(options) {
        const scriptMode = options.scriptMode;
        const isPro = options.isPro;
        const selectedPrompt = options.selectedPrompt;
        const promptSetLookup = options.promptSetLookup || {};
        const detectedSpeakers = options.detectedSpeakers || [];
        const speakerMap = options.speakerMap || {};
        const plainText = options.plainText || '';
        const unlabeledParagraphCount = options.unlabeledParagraphCount || 0;
        const emptyDialogueCount = options.emptyDialogueCount || 0;
        const mixedSpeakerBlockCount = options.mixedSpeakerBlockCount || 0;
        const criticalErrors = [];
        const qualityWarnings = [];

        if (selectedPrompt && promptSetLookup[selectedPrompt]) {
            const promptSpeakerType = promptSetLookup[selectedPrompt].speaker_type || 'single';
            if (promptSpeakerType !== scriptMode) {
                criticalErrors.push(__('The current generation mode does not match the speaker type of the Prompt Set.', 'quikvox-ai'));
            }
        }

        if (scriptMode === 'multi') {
            if (!isPro) {
                criticalErrors.push(__('Multi Speaker is available only on the Pro and Business plans.', 'quikvox-ai'));
            }

            if (detectedSpeakers.length !== 2) {
                criticalErrors.push(__('Multi Speaker requires exactly two detected speakers.', 'quikvox-ai'));
            }

            if (detectedSpeakers.length > 2) {
                criticalErrors.push(__('Gemini TTS cannot handle three or more speakers.', 'quikvox-ai'));
            }

            const unmappedSpeakers = detectedSpeakers.filter((speakerName) => !speakerMap[normalizeSpeakerNameKey(speakerName)]);
            if (unmappedSpeakers.length > 0) {
                criticalErrors.push(sprintf(__('There are unmapped speakers: %s', 'quikvox-ai'), unmappedSpeakers.join(', ')));
            }

            if (unlabeledParagraphCount > 0) {
                qualityWarnings.push(sprintf(__('There are %d paragraphs without a speaker label.', 'quikvox-ai'), unlabeledParagraphCount));
            }

            if (emptyDialogueCount > 0) {
                qualityWarnings.push(sprintf(__('There are %d speaker lines with empty dialogue.', 'quikvox-ai'), emptyDialogueCount));
            }

            if (mixedSpeakerBlockCount > 0) {
                qualityWarnings.push(sprintf(__('There are %d paragraphs with multiple speakers in one block. Please split blocks by speaker.', 'quikvox-ai'), mixedSpeakerBlockCount));
            }
        }

        if (/[<>{}\[\]\|]{3,}/.test(plainText)) {
            qualityWarnings.push(__('The text contains many symbols that TTS does not handle well.', 'quikvox-ai'));
        }

        return {
            criticalErrors,
            qualityWarnings,
        };
    }

    // EMOTION_TAGS removed (emotion tag feature removed)
    /**
     * Component: Source Article Panel (Left Side Column)
     */
    const SourceArticlePanel = ({ title, content }) => {
        if (!title && !content) return null;

        return createElement('div', { className: 'Podcastmaker-source-panel' },
            createElement('div', { className: 'panel-header' },
                createElement('span', { className: 'panel-header-label' }, __('Source Article', 'quikvox-ai'))
            ),
            // Scrollable Body - V-06: XSS対策のためプレーンテキストとして描画する
            createElement('div', { className: 'panel-body' },
                createElement('h2', {}, title),
                createElement('div', { className: 'source-content' }, stripHtmlTags(content))
            )
        );
    };

    /**
     * Main Class: Podcaster Sidebar & Editor Logic
     */
    class PodcasterSidebar extends Component {
        constructor() {
            super(...arguments);

            const locData = window.PodcastmakerScriptData || {};
            const initialMode = normalizeScriptMode('single', !!locData.isPro || !!locData.isBusiness);
            const initialPromptSetId = getDefaultPromptSetId(locData, initialMode);
            const initialPromptSet = locData.promptSets && locData.promptSets[initialPromptSetId]
                ? locData.promptSets[initialPromptSetId]
                : null;
            const initialLanguage = initialPromptSet ? getPromptSetLanguage(initialPromptSet) : getDefaultPreviewLanguage(locData.siteLocale || locData.locale || '');
            const initialGeminiModels = Array.isArray(locData.geminiModels) ? locData.geminiModels : [];
            const initialTtsModels = extractModelValues(locData.ttsModels || []);

            this.state = {
                promptSets: [],
                promptSetLookup: locData.promptSets || {},
                voices: [],
                isGeneratingScript: false,
                isGeneratingVoice: false,
                isInserting: false,
                isEmbedded: false,
                generatedAudioUrl: '',
                generatedAttachId: 0,
                generatedVoiceSummaryLabel: '',

                // Selection States
                selectedPrompt: initialPromptSetId,
                selectedLanguage: initialLanguage,
                selectedLength: '30sec',
                selectedModel: resolveAvailableModel(locData.currentGeminiModel || '', locData.geminiModels || [], ''),
                selectedVoice: '',
                scriptMode: initialMode,
                multiSpeakerMap: {},
                selectedTtsModel: resolveAvailableModel(locData.defaultTtsModel || '', initialTtsModels, 'gemini-2.5-flash-preview-tts'),
                isContextLayerOpen: false,
                contextDraft: null,
                isVoiceLayerOpen: false,
                isSmartToneLayerOpen: false,

                // Data Lists
                geminiModels: initialGeminiModels,
                ttsModels: locData.ttsModels || [],
                sourcePosts: [],

                // Preview States
                playingVoice: null,
                previewLang: getDefaultPreviewLanguage(locData.siteLocale || locData.locale || ''),
                assetsUrl: locData.assetsUrl || '',
                activeVoiceGender: 'Male',

                // Source Post Data
                sourcePostId: '',
                sourceTitle: '',
                sourceContent: '',
                isMobile: window.innerWidth <= 782, // Initialize mobile state
                lastError: null,
                detectedSpeakers: [],
                blockSpeakerInfo: {},
                validationState: {
                    criticalErrors: [],
                    qualityWarnings: [],
                },

                // Smart Tone States
                selectedToneStyle: getDefaultSmartToneStyleId(locData, initialMode),
                isApplyingSmartTone: false,
                hasAppliedSmartTone: false,
                isPro: locData.isPro || false,
                isBusiness: locData.isBusiness || false,
                smartToneStyles: locData.smartToneStyles || [],
                isVoiceDirectionOpen: false,
                showPayloadPreview: false,
                payloadCopyStatus: '',
                pendingSmartToneStyleChange: null,
                generationProgress: null,
            };

            this.handleGenerateScript = this.handleGenerateScript.bind(this);
            this.handleGenerateVoice = this.handleGenerateVoice.bind(this);
            this.handleApplySmartTone = this.handleApplySmartTone.bind(this);
            this.handleInsertToSource = this.handleInsertToSource.bind(this);
            this.handleResize = this.handleResize.bind(this);
            this.togglePreview = this.togglePreview.bind(this);
            this.stopAllPreview = this.stopAllPreview.bind(this);
            this.autoOpenSidebarFromUrl = this.autoOpenSidebarFromUrl.bind(this);
            this.handleVoiceSelection = this.handleVoiceSelection.bind(this);
            this.handleVoiceGenderChange = this.handleVoiceGenderChange.bind(this);
            this.handleTtsModelChange = this.handleTtsModelChange.bind(this);
            this.handleModeChange = this.handleModeChange.bind(this);
            this.handleSpeakerVoiceChange = this.handleSpeakerVoiceChange.bind(this);
            this.handleContextLayerOpen = this.handleContextLayerOpen.bind(this);
            this.handleContextLayerClose = this.handleContextLayerClose.bind(this);
            this.handleContextDraftChange = this.handleContextDraftChange.bind(this);
            this.handleContextLayerApply = this.handleContextLayerApply.bind(this);
            this.handleContextLayerSelect = this.handleContextLayerSelect.bind(this);
            this.handleContextLayerCreate = this.handleContextLayerCreate.bind(this);
            this.handleVoiceLayerOpen = this.handleVoiceLayerOpen.bind(this);
            this.handleVoiceLayerClose = this.handleVoiceLayerClose.bind(this);
            this.handleVoiceLayerSelect = this.handleVoiceLayerSelect.bind(this);
            this.handleVoiceLayerCreate = this.handleVoiceLayerCreate.bind(this);
            this.handleSmartToneLayerOpen = this.handleSmartToneLayerOpen.bind(this);
            this.handleSmartToneLayerClose = this.handleSmartToneLayerClose.bind(this);
            this.handleSmartToneLayerSelect = this.handleSmartToneLayerSelect.bind(this);
            this.handleSmartToneLayerCreate = this.handleSmartToneLayerCreate.bind(this);
            this.refreshSpeakerState = this.refreshSpeakerState.bind(this);
            this.applySpeakerBlockDecorations = this.applySpeakerBlockDecorations.bind(this);
            this.handleVoiceDirectionChange = this.handleVoiceDirectionChange.bind(this);
            this.handleSmartToneStyleChange = this.handleSmartToneStyleChange.bind(this);
            this.confirmSmartToneStyleChange = this.confirmSmartToneStyleChange.bind(this);
            this.cancelSmartToneStyleChange = this.cancelSmartToneStyleChange.bind(this);
            this.handleToggleVoiceDirection = this.handleToggleVoiceDirection.bind(this);
            this.handleTogglePayloadPreview = this.handleTogglePayloadPreview.bind(this);
            this.handleCopyPayloadPreview = this.handleCopyPayloadPreview.bind(this);
            this.handleMultiSpeakerVoiceGeneration = this.handleMultiSpeakerVoiceGeneration.bind(this);
            this.handleScriptPaste = this.handleScriptPaste.bind(this);
            this.ensureDefaultVoiceDirectionTemplate = this.ensureDefaultVoiceDirectionTemplate.bind(this);
            this.renderGenerationProgressPopup = this.renderGenerationProgressPopup.bind(this);
            this.startGenerationProgress = this.startGenerationProgress.bind(this);
            this.updateGenerationProgress = this.updateGenerationProgress.bind(this);
            this.finishGenerationProgress = this.finishGenerationProgress.bind(this);
            this.failGenerationProgress = this.failGenerationProgress.bind(this);
            this.closeGenerationProgress = this.closeGenerationProgress.bind(this);
            this.clearGenerationProgressTimers = this.clearGenerationProgressTimers.bind(this);

            this.currentAudio = null;
            this.autoOpenSidebarAttempts = 0;
            this.autoOpenSidebarMaxAttempts = 10;
            this.autoOpenSidebarRequested = false;
            this.decorationFrame = null;
            this.generationProgressTimer = null;
            this.generationProgressCloseTimer = null;
            this.generationProgressStartedAt = 0;
            this.generationProgressEstimatedSeconds = 0;
            this.generationProgressPhaseStartedAt = 0;
            this.generationProgressChunkEstimatedSeconds = 40;
            this.generationProgressFinalizeEstimatedSeconds = 10;
            this.generationProgressPrepareEstimatedSeconds = 8;
            this.multiSpeakerNormalizationFrame = null;
            this.isNormalizingMultiSpeakerBlocks = false;
        }

        componentDidMount() {
            this.fetchSourcePosts();
            this.fetchVoices();
            this.fetchPromptSets();

            window.addEventListener('resize', this.handleResize);
            document.addEventListener('paste', this.handleScriptPaste, true);
            this.autoOpenSidebarFromUrl();

            const meta = this.props.meta;
            const locData = window.PodcastmakerScriptData || {};
            if (meta) {
                const savedScriptMode = normalizeScriptMode(meta._Podcastmaker_script_mode || 'single', !!locData.isPro || !!locData.isBusiness);
                const savedPromptSetId = this.state.selectedPrompt || getDefaultPromptSetId(locData, savedScriptMode);
                const savedPromptSet = locData.promptSets && locData.promptSets[savedPromptSetId]
                    ? locData.promptSets[savedPromptSetId]
                    : null;
                const savedLanguage = normalizeLanguageCode(
                    meta._Podcastmaker_script_language
                    || (savedPromptSet ? savedPromptSet.language : '')
                    || this.state.selectedLanguage
                    || getDefaultPreviewLanguage(locData.siteLocale || locData.locale || '')
                );
                const savedLength = normalizeLengthChoice(meta._Podcastmaker_script_length || this.state.selectedLength);
                const resolvedPromptSetId = this.getAvailablePromptSetId(savedLanguage, savedScriptMode, savedPromptSetId)
                    || savedPromptSetId;
                const nextMetaState = {
                    scriptMode: savedScriptMode,
                    multiSpeakerMap: parseSpeakerMapJson(meta._Podcastmaker_multi_speaker_map),
                    generatedVoiceSummaryLabel: normalizeVoiceSummaryLabel(meta._Podcastmaker_voice_summary_label || ''),
                    hasAppliedSmartTone: meta._Podcastmaker_smart_tone_applied === '1' || meta._Podcastmaker_smart_tone_applied === 'true',
                    selectedPrompt: resolvedPromptSetId,
                    selectedLanguage: savedLanguage,
                    selectedLength: savedLength,
                };
                if (meta._Podcastmaker_source_post_id) {
                    nextMetaState.sourcePostId = meta._Podcastmaker_source_post_id;
                    this.fetchSourceContent(meta._Podcastmaker_source_post_id);
                }
                if (meta._Podcastmaker_voice_id) {
                    const initialVoiceGender = getVoiceGenderById(locData.voices || [], meta._Podcastmaker_voice_id);
                    const nextSelectionState = {
                        ...nextMetaState,
                        selectedVoice: meta._Podcastmaker_voice_id
                    };

                    if (initialVoiceGender) {
                        nextSelectionState.activeVoiceGender = initialVoiceGender;
                    }

                    this.setState(nextSelectionState, this.refreshSpeakerState);
                } else {
                    this.setState(nextMetaState, this.refreshSpeakerState);
                }
                if (meta._Podcastmaker_audio_url) this.setState({ generatedAudioUrl: meta._Podcastmaker_audio_url });
                if (meta._Podcastmaker_audio_id) this.setState({ generatedAttachId: meta._Podcastmaker_audio_id });
                if (meta._Podcastmaker_audio_embedded === '1' || meta._Podcastmaker_audio_embedded === 'true') {
                    this.setState({ isEmbedded: true });
                }
            }

            // Watcher for header buttons (Embed in Article)
            this.headerWatcher = setInterval(() => {
                this.updateHeaderUI();
            }, 500);

            // Hide standard panels programmatically as a backup to CSS
            setTimeout(() => {
                // Use core/editor instead of core/edit-post for removeEditorPanel
                const editorDispatch = wp.data.dispatch('core/editor');
                if (editorDispatch && editorDispatch.removeEditorPanel) {
                    const panelsToRemove = ['post-link', 'page-attributes', 'featured-image', 'discussion-panel', 'post-excerpt', 'taxonomy-panel-category', 'taxonomy-panel-post_tag'];
                    panelsToRemove.forEach(p => {
                        try {
                            editorDispatch.removeEditorPanel(p);
                        } catch (e) {
                            // Panel may not exist, ignore
                        }
                    });
                }

                // Restrict blocks to just Paragraph for scripts
                if (wp.data && wp.data.dispatch('core/blocks')) {
                    try {
                        wp.data.dispatch('core/blocks').setDefaultBlockName('core/paragraph');
                    } catch (e) {
                        console.log('Podcastmaker: Could not set default block');
                    }
                }
                this.refreshSpeakerState();
            }, 1000);

            this.ensureDefaultVoiceDirectionTemplate();
        }

        componentWillUnmount() {
            if (this.headerWatcher) clearInterval(this.headerWatcher);
            window.removeEventListener('resize', this.handleResize);
            document.removeEventListener('paste', this.handleScriptPaste, true);
            if (this.multiSpeakerNormalizationFrame) {
                window.cancelAnimationFrame(this.multiSpeakerNormalizationFrame);
            }
            if (this.decorationFrame) {
                window.cancelAnimationFrame(this.decorationFrame);
            }
            this.clearGenerationProgressTimers();
        }

        scheduleMultiSpeakerBlockNormalization() {
            if (this.multiSpeakerNormalizationFrame) {
                return;
            }

            this.multiSpeakerNormalizationFrame = window.requestAnimationFrame(() => {
                this.multiSpeakerNormalizationFrame = null;
                this.normalizeMultiSpeakerParagraphBlocks();
            });
        }

        normalizeMultiSpeakerParagraphBlocks() {
            if (this.isNormalizingMultiSpeakerBlocks) {
                return;
            }

            const scriptMode = normalizeScriptMode(this.state.scriptMode, this.state.isPro || this.state.isBusiness);
            if (scriptMode !== 'multi' || !wp || !wp.data || !wp.blocks) {
                return;
            }

            const select = wp.data.select('core/block-editor');
            const dispatch = wp.data.dispatch('core/block-editor');
            if (!select || !dispatch || !dispatch.replaceBlocks) {
                return;
            }

            const blocks = select.getBlocks ? select.getBlocks() : [];
            const paragraphBlocks = Array.isArray(blocks)
                ? blocks.filter((block) => block && block.name === 'core/paragraph')
                : [];

            const blocksToSplit = paragraphBlocks
                .map((block) => {
                    const plainText = getPlainTextFromBlockContent(block.attributes && block.attributes.content ? block.attributes.content : '').trim();
                    const parsedTurns = parseDialogueTurnsFromText(plainText);
                    return {
                        block,
                        parsedTurns,
                    };
                })
                .filter((item) => item.block && Array.isArray(item.parsedTurns) && item.parsedTurns.length > 1);

            if (blocksToSplit.length === 0) {
                return;
            }

            this.isNormalizingMultiSpeakerBlocks = true;

            try {
                blocksToSplit.reverse().forEach((item) => {
                    const replacementBlocks = item.parsedTurns.map((turn) => wp.blocks.createBlock('core/paragraph', {
                        content: normalizeSpeakerTaggedLine(`${turn.speaker}: ${turn.text}`),
                    }));
                    dispatch.replaceBlocks(item.block.clientId, replacementBlocks);
                });
            } finally {
                window.setTimeout(() => {
                    this.isNormalizingMultiSpeakerBlocks = false;
                }, 0);
            }
        }

        handleScriptPaste(event) {
            if (!event || !event.clipboardData || !wp || !wp.data || !wp.blocks) {
                return;
            }

            const scriptMode = normalizeScriptMode(this.state.scriptMode, this.state.isPro || this.state.isBusiness);
            if (scriptMode !== 'multi') {
                return;
            }

            const clipboardText = String(event.clipboardData.getData('text/plain') || '').replace(/\r\n|\r/g, '\n').trim();
            if (!clipboardText) {
                return;
            }

            const paragraphTexts = splitScriptPasteIntoParagraphTexts(clipboardText, scriptMode);
            if (!Array.isArray(paragraphTexts) || paragraphTexts.length <= 1) {
                return;
            }

            const select = wp.data.select('core/block-editor');
            const dispatch = wp.data.dispatch('core/block-editor');
            if (!select || !dispatch || !dispatch.replaceBlocks) {
                return;
            }

            const eventTarget = event.target || document.activeElement || null;
            const selectedBlockClientId = getPasteBlockClientIdFromTarget(eventTarget)
                || (select.getSelectedBlockClientId ? select.getSelectedBlockClientId() : '');
            if (!selectedBlockClientId) {
                return;
            }

            const selectedBlock = select.getBlock ? select.getBlock(selectedBlockClientId) : null;
            if (!selectedBlock || selectedBlock.name !== 'core/paragraph') {
                return;
            }

            event.preventDefault();

            const replacementBlocks = paragraphTexts.map((paragraphText) => wp.blocks.createBlock('core/paragraph', {
                content: paragraphText,
            }));

            dispatch.replaceBlocks(selectedBlockClientId, replacementBlocks);
        }

        autoOpenSidebarFromUrl() {
            if (this.autoOpenSidebarRequested) {
                return;
            }

            const urlParams = new URLSearchParams(window.location.search);
            const shouldAutoOpen = urlParams.get('podcastmaker_open') === '1' && !!urlParams.get('source_post_id');
            if (!shouldAutoOpen || !wp.data || !wp.data.select || !wp.data.dispatch) {
                return;
            }

            this.autoOpenSidebarRequested = true;
            const sidebarId = 'podcastmaker-sidebar/podcaster-sidebar';

            const openSidebar = () => {
                const editPostSelect = wp.data.select('core/edit-post');
                const editPostDispatch = wp.data.dispatch('core/edit-post');

                if (!editPostSelect || !editPostDispatch || !editPostDispatch.openGeneralSidebar) {
                    if (this.autoOpenSidebarAttempts < this.autoOpenSidebarMaxAttempts) {
                        this.autoOpenSidebarAttempts += 1;
                        window.setTimeout(openSidebar, 250);
                    }
                    return;
                }

                if (editPostSelect.getActiveGeneralSidebarName() !== sidebarId) {
                    editPostDispatch.openGeneralSidebar(sidebarId);
                }

                window.setTimeout(() => {
                    window.dispatchEvent(new Event('resize'));
                }, 100);

                if (editPostSelect.getActiveGeneralSidebarName() !== sidebarId && this.autoOpenSidebarAttempts < this.autoOpenSidebarMaxAttempts) {
                    this.autoOpenSidebarAttempts += 1;
                    window.setTimeout(openSidebar, 250);
                }
            };

            window.setTimeout(openSidebar, 300);
        }

        handleResize() {
            const isMobile = window.innerWidth <= 782;
            if (this.state.isMobile !== isMobile) {
                this.setState({ isMobile });
            }
        }

        componentDidUpdate(prevProps, prevState) {
            const meta = this.props.meta;
            const prevMeta = prevProps.meta;

            if (meta && (!prevMeta || meta._Podcastmaker_source_post_id !== prevMeta._Podcastmaker_source_post_id)) {
                if (meta._Podcastmaker_source_post_id && meta._Podcastmaker_source_post_id !== this.state.sourcePostId) {
                    this.setState({ sourcePostId: meta._Podcastmaker_source_post_id });
                }
            }

            if (this.state.sourcePostId !== prevState.sourcePostId) {
                this.fetchSourceContent(this.state.sourcePostId);
            }

            if (meta && prevMeta) {
                if (meta._Podcastmaker_script_mode !== prevMeta._Podcastmaker_script_mode && meta._Podcastmaker_script_mode !== this.state.scriptMode) {
                    const nextScriptMode = normalizeScriptMode(meta._Podcastmaker_script_mode, this.state.isPro || this.state.isBusiness);
                    const nextPromptSetId = this.getAvailablePromptSetId(this.state.selectedLanguage, nextScriptMode, this.state.selectedPrompt);

                    this.setState({
                        scriptMode: nextScriptMode,
                        selectedPrompt: nextPromptSetId || this.state.selectedPrompt,
                    });
                }

                if (meta._Podcastmaker_script_language !== prevMeta._Podcastmaker_script_language && meta._Podcastmaker_script_language !== this.state.selectedLanguage) {
                    const nextLanguage = normalizeLanguageCode(meta._Podcastmaker_script_language);
                    const nextPromptSetId = this.getAvailablePromptSetId(nextLanguage, this.state.scriptMode, this.state.selectedPrompt);

                    this.setState({
                        selectedLanguage: nextLanguage,
                        selectedPrompt: nextPromptSetId || this.state.selectedPrompt,
                    });
                }

                if (meta._Podcastmaker_script_length !== prevMeta._Podcastmaker_script_length && meta._Podcastmaker_script_length !== this.state.selectedLength) {
                    this.setState({ selectedLength: normalizeLengthChoice(meta._Podcastmaker_script_length) });
                }

                if (meta._Podcastmaker_multi_speaker_map !== prevMeta._Podcastmaker_multi_speaker_map) {
                    this.setState({ multiSpeakerMap: parseSpeakerMapJson(meta._Podcastmaker_multi_speaker_map) });
                }
            }

            const shouldRefreshSpeakerState =
                prevProps.blocks !== this.props.blocks
                || prevState.scriptMode !== this.state.scriptMode
                || prevState.selectedPrompt !== this.state.selectedPrompt
                || prevState.multiSpeakerMap !== this.state.multiSpeakerMap;

            if (prevProps.blocks !== this.props.blocks) {
                this.scheduleMultiSpeakerBlockNormalization();
            }

            if (shouldRefreshSpeakerState) {
                this.refreshSpeakerState();
            }

            const shouldDecorateSpeakerBlocks =
                prevProps.blocks !== this.props.blocks
                || prevState.blockSpeakerInfo !== this.state.blockSpeakerInfo
                || prevState.scriptMode !== this.state.scriptMode;

            if (shouldDecorateSpeakerBlocks) {
                if (this.decorationFrame) {
                    window.cancelAnimationFrame(this.decorationFrame);
                }

                this.decorationFrame = window.requestAnimationFrame(() => {
                    this.applySpeakerBlockDecorations();
                });
            }

            const shouldEnsureDefaultVoiceDirection =
                prevState.selectedToneStyle !== this.state.selectedToneStyle
                || prevState.scriptMode !== this.state.scriptMode
                || prevState.smartToneStyles !== this.state.smartToneStyles
                || prevProps.meta !== this.props.meta;

            if (shouldEnsureDefaultVoiceDirection) {
                this.ensureDefaultVoiceDirectionTemplate(prevState);
            }
        }

        ensureDefaultVoiceDirectionTemplate(prevState) {
            if (!this.state.isPro && !this.state.isBusiness) {
                return;
            }

            const currentVoiceDirection = this.props.meta && this.props.meta._Podcastmaker_voice_direction
                ? String(this.props.meta._Podcastmaker_voice_direction).trim()
                : '';
            if (currentVoiceDirection === '') {
                return;
            }

            const activeTemplate = getActiveVoiceDirectionTemplate(
                this.state.smartToneStyles,
                this.state.scriptMode,
                this.state.selectedToneStyle
            );
            const previousTemplate = getActiveVoiceDirectionTemplate(
                this.state.smartToneStyles,
                prevState && prevState.scriptMode ? prevState.scriptMode : this.state.scriptMode,
                prevState && prevState.selectedToneStyle ? prevState.selectedToneStyle : this.state.selectedToneStyle
            );

            if (!activeTemplate) {
                return;
            }

            if (currentVoiceDirection === activeTemplate) {
                return;
            }

            if (currentVoiceDirection && currentVoiceDirection !== previousTemplate) {
                return;
            }

            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_voice_direction: activeTemplate,
                }
            });
        }

        refreshSpeakerState() {
            const speakerAnalysis = analyzeSpeakerBlocks(this.props.blocks);
            const plainText = getScriptPlainTextFromBlocks(this.props.blocks);
            const normalizedMultiSpeakerMap = this.normalizeMultiSpeakerMap(speakerAnalysis.detectedSpeakers, this.state.multiSpeakerMap);
            const validationState = collectValidationState({
                scriptMode: this.state.scriptMode,
                isPro: this.state.isPro || this.state.isBusiness,
                selectedPrompt: this.state.selectedPrompt,
                promptSetLookup: this.state.promptSetLookup,
                detectedSpeakers: speakerAnalysis.detectedSpeakers,
                speakerMap: normalizedMultiSpeakerMap,
                plainText,
                unlabeledParagraphCount: speakerAnalysis.unlabeledParagraphCount,
                emptyDialogueCount: speakerAnalysis.emptyDialogueCount,
                mixedSpeakerBlockCount: speakerAnalysis.mixedSpeakerBlockCount,
            });

            const nextState = {
                detectedSpeakers: speakerAnalysis.detectedSpeakers,
                blockSpeakerInfo: speakerAnalysis.blockSpeakerInfo,
                validationState,
            };

            if (!this.areStringMapsEqual(this.state.multiSpeakerMap, normalizedMultiSpeakerMap)) {
                nextState.multiSpeakerMap = normalizedMultiSpeakerMap;
            }

            if (
                JSON.stringify(this.state.detectedSpeakers) !== JSON.stringify(nextState.detectedSpeakers)
                || JSON.stringify(this.state.blockSpeakerInfo) !== JSON.stringify(nextState.blockSpeakerInfo)
                || JSON.stringify(this.state.validationState) !== JSON.stringify(nextState.validationState)
                || !this.areStringMapsEqual(this.state.multiSpeakerMap, normalizedMultiSpeakerMap)
            ) {
                this.setState(nextState);

                if (!this.areStringMapsEqual(this.state.multiSpeakerMap, normalizedMultiSpeakerMap)) {
                    this.props.editPost({
                        meta: {
                            ...this.props.meta,
                            _Podcastmaker_multi_speaker_map: JSON.stringify(normalizedMultiSpeakerMap),
                        }
                    });
                }
            }
        }

        applySpeakerBlockDecorations() {
            const editorDocuments = getEditorCanvasDocuments();
            const speakerColors = [
                'rgba(34, 113, 177, 0.14)',
                'rgba(0, 163, 42, 0.14)',
                'rgba(235, 160, 0, 0.16)',
            ];
            const speakerBorders = [
                'inset 0 0 0 1px rgba(34, 113, 177, 0.18)',
                'inset 0 0 0 1px rgba(0, 163, 42, 0.18)',
                'inset 0 0 0 1px rgba(235, 160, 0, 0.22)',
            ];
            const speakerAccents = [
                '#2271b1',
                '#00a32a',
                '#eba000',
            ];
            const speakerColorAssignments = {};
            let nextColorIndex = 0;

            editorDocuments.forEach((editorDocument) => {
                const paragraphBlocks = editorDocument.querySelectorAll('.block-editor-block-list__block[data-type="core/paragraph"]');
                paragraphBlocks.forEach((blockNode) => {
                    blockNode.classList.remove('pm-speaker-block', 'pm-speaker-block--speaker-0', 'pm-speaker-block--speaker-1', 'pm-speaker-block--speaker-2');
                    blockNode.removeAttribute('data-pm-speaker');
                    blockNode.style.backgroundColor = '';
                    blockNode.style.boxShadow = '';
                    blockNode.style.borderLeft = '';
                    blockNode.style.borderRadius = '';
                    const decoratedNodes = blockNode.querySelectorAll('.pm-speaker-block__surface, .pm-speaker-block__surface--speaker-0, .pm-speaker-block__surface--speaker-1, .pm-speaker-block__surface--speaker-2');
                    decoratedNodes.forEach((targetNode) => {
                        targetNode.classList.remove('pm-speaker-block__surface', 'pm-speaker-block__surface--speaker-0', 'pm-speaker-block__surface--speaker-1', 'pm-speaker-block__surface--speaker-2');
                        targetNode.style.backgroundColor = '';
                        targetNode.style.boxShadow = '';
                        targetNode.style.borderRadius = '';
                        targetNode.style.borderLeft = '';
                    });
                });
            });

            editorDocuments.forEach((editorDocument) => {
                const paragraphBlocks = editorDocument.querySelectorAll('.block-editor-block-list__block[data-type="core/paragraph"]');
                paragraphBlocks.forEach((blockNode, blockIndex) => {
                    if (!blockNode) {
                        return;
                    }

                    const blockClientId = blockNode.getAttribute('data-block') || '';
                    const speakerInfo = blockClientId ? this.state.blockSpeakerInfo[blockClientId] : null;
                    const speakerName = speakerInfo && speakerInfo.speaker ? String(speakerInfo.speaker).trim() : '';
                    const normalizedSpeakerKey = speakerInfo && speakerInfo.speakerKey
                        ? String(speakerInfo.speakerKey).trim()
                        : normalizeSpeakerNameKey(speakerName);
                    const speakerKey = normalizedSpeakerKey || `block-${blockIndex + 1}`;

                    if (typeof speakerColorAssignments[speakerKey] === 'undefined') {
                        speakerColorAssignments[speakerKey] = nextColorIndex % speakerColors.length;
                        nextColorIndex += 1;
                    }

                    const colorIndex = speakerColorAssignments[speakerKey];
                    blockNode.classList.add('pm-speaker-block', `pm-speaker-block--speaker-${colorIndex}`);
                    blockNode.setAttribute('data-pm-speaker', speakerKey);
                    blockNode.style.backgroundColor = speakerColors[colorIndex] || speakerColors[0];
                    blockNode.style.boxShadow = speakerBorders[colorIndex] || speakerBorders[0];
                    blockNode.style.borderLeft = `4px solid ${speakerAccents[colorIndex] || speakerAccents[0]}`;
                    blockNode.style.borderRadius = '12px';

                    const editableTargets = blockNode.querySelectorAll(
                        '.block-editor-block-list__block-edit, .block-editor-block-list__layout, .block-editor-rich-text__editable, .wp-block-paragraph, [contenteditable="true"]'
                    );
                    editableTargets.forEach((targetNode) => {
                        targetNode.classList.add('pm-speaker-block__surface', `pm-speaker-block__surface--speaker-${colorIndex}`);
                        targetNode.style.backgroundColor = speakerColors[colorIndex] || speakerColors[0];
                        targetNode.style.boxShadow = speakerBorders[colorIndex] || speakerBorders[0];
                        targetNode.style.borderRadius = '12px';
                        targetNode.style.borderLeft = `4px solid ${speakerAccents[colorIndex] || speakerAccents[0]}`;
                    });
                });
            });
        }

        updateHeaderUI() {
            try {
                // Ensure Sidebar stays on "Document" tab
                const { getActiveGeneralSidebarName } = wp.data.select('core/edit-post');
                const { openGeneralSidebar } = wp.data.dispatch('core/edit-post');
                if (getActiveGeneralSidebarName() === 'edit-post/block') {
                    openGeneralSidebar('edit-post/document');
                }
            } catch (e) { }

            // Rename Publish button to "Embed in Article"
            const publishBtns = document.querySelectorAll('.editor-post-publish-button, .editor-post-publish-panel__toggle');
            publishBtns.forEach(btn => {
                if (btn.innerText !== __('Embed in Article', 'quikvox-ai') && btn.innerText !== __('Updating...', 'quikvox-ai')) {
                    btn.innerText = __('Embed in Article', 'quikvox-ai');
                }

                // Hook the click event
                if (!btn.hasAttribute('data-hooked')) {
                    btn.addEventListener('click', (e) => {
                        if (this.state.generatedAudioUrl) {
                            e.preventDefault();
                            e.stopPropagation();
                            this.handleInsertToSource();
                        }
                    }, true);
                    btn.setAttribute('data-hooked', 'true');
                }

                // UI feedback based on audio availability
                if (!this.state.generatedAudioUrl) {
                    btn.setAttribute('disabled', 'disabled');
                    btn.style.opacity = '0.5';
                } else {
                    btn.removeAttribute('disabled');
                    btn.style.opacity = '1';
                }
            });

            // Disable Save Draft if nothing to save
            const saveBtn = document.querySelector('.editor-post-save-draft');
            if (saveBtn) {
                const isDirty = wp.data.select('core/editor').isEditedPostDirty();
                if (!isDirty || wp.data.select('core/editor').isSavingPost()) {
                    saveBtn.setAttribute('disabled', 'disabled');
                    saveBtn.style.opacity = '0.5';
                } else {
                    saveBtn.removeAttribute('disabled');
                    saveBtn.style.opacity = '1';
                }
            }
        }

        fetchSourceContent(id) {
            if (!id) return;

            // Try to fetch as post first, then as page if it fails
            wp.apiFetch({ path: `/wp/v2/posts/${id}` }).then(post => {
                this.setState({
                    sourceTitle: post.title.rendered,
                    sourceContent: post.content.rendered
                });
            }).catch(err => {
                // If post fetch fails, try as page
                wp.apiFetch({ path: `/wp/v2/pages/${id}` }).then(page => {
                    this.setState({
                        sourceTitle: page.title.rendered,
                        sourceContent: page.content.rendered
                    });
                }).catch(err2 => {
                    console.error('Failed to load source post/page', err2);
                });
            });
        }

        fetchPromptSets() {
            const data = window.PodcastmakerScriptData || {};
            if (data.promptSets) {
                const promptSetEntries = buildPromptSetEntries(data.promptSets);
                const selectedPromptSetId = this.state.selectedPrompt || getDefaultPromptSetId(data, this.state.scriptMode);
                const selectedPromptSet = selectedPromptSetId && data.promptSets[selectedPromptSetId] ? data.promptSets[selectedPromptSetId] : null;
                const selectedLanguage = normalizeLanguageCode(
                    this.state.selectedLanguage
                    || (selectedPromptSet ? selectedPromptSet.language : '')
                    || getDefaultPreviewLanguage(data.siteLocale || data.locale || '')
                );
                const selectedPrompt = resolvePromptSetIdForContext(
                    promptSetEntries,
                    data.promptSets,
                    selectedLanguage,
                    this.state.scriptMode,
                    selectedPromptSetId
                ) || selectedPromptSetId;

                this.setState({
                    promptSets: promptSetEntries,
                    promptSetLookup: data.promptSets,
                    selectedPrompt,
                    selectedLanguage,
                }, this.refreshSpeakerState);
            }
        }

        fetchVoices() {
            const data = window.PodcastmakerScriptData || {};
            if (data.voices) {
                // Filter and sort voices (Male first, then Female)
                const rawVoices = data.voices || [];
                const maleVoices = rawVoices.filter(v => v.gender === 'Male');
                const femaleVoices = rawVoices.filter(v => v.gender === 'Female');
                const sortedVoices = maleVoices.concat(femaleVoices);
                const selectedVoiceGender = getVoiceGenderById(sortedVoices, this.state.selectedVoice);
                const defaultVoiceId = this.state.selectedVoice || (maleVoices[0] && maleVoices[0].voice_id ? maleVoices[0].voice_id : (sortedVoices[0] && sortedVoices[0].voice_id ? sortedVoices[0].voice_id : ''));
                const nextVoiceState = {
                    voices: maleVoices.concat(femaleVoices),
                    rawVoices: rawVoices // Keep original list for other uses if needed
                };

                if (!this.state.selectedVoice && defaultVoiceId) {
                    nextVoiceState.selectedVoice = defaultVoiceId;
                }

                if (selectedVoiceGender) {
                    nextVoiceState.activeVoiceGender = selectedVoiceGender;
                } else if (defaultVoiceId) {
                    nextVoiceState.activeVoiceGender = getVoiceGenderById(sortedVoices, defaultVoiceId) || 'Male';
                }

                this.setState(nextVoiceState, this.refreshSpeakerState);
            }
        }

        getSelectedVoiceGender(voiceId) {
            if (!voiceId) {
                return '';
            }

            const voiceGender = getVoiceGenderById(this.state.voices, voiceId);

            if (voiceGender) {
                return voiceGender;
            }

            return getVoiceGenderById((window.PodcastmakerScriptData || {}).voices || [], voiceId);
        }

        getAvailableVoiceList() {
            if (Array.isArray(this.state.voices) && this.state.voices.length > 0) {
                return this.state.voices;
            }

            const scriptData = window.PodcastmakerScriptData || {};
            return Array.isArray(scriptData.voices) ? scriptData.voices : [];
        }

        getDefaultVoiceIdByGender(gender) {
            const normalizedGender = gender === 'Female' ? 'Female' : 'Male';
            const availableVoices = this.getAvailableVoiceList();
            const voicesByGender = availableVoices.filter((voice) => voice && voice.gender === normalizedGender);
            if (voicesByGender.length > 0) {
                return voicesByGender[0].voice_id || '';
            }

            return availableVoices.length > 0 ? (availableVoices[0].voice_id || '') : '';
        }

        getMultiSpeakerSelection(speakerName) {
            const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
            const availableVoices = this.getAvailableVoiceList();
            const speakerIndex = Math.max(0, (Array.isArray(this.state.detectedSpeakers) ? this.state.detectedSpeakers : []).findIndex((name) => normalizeSpeakerNameKey(name) === normalizedSpeakerName));
            const defaultVoice = availableVoices[speakerIndex] || availableVoices[0] || null;
            const defaultVoiceId = defaultVoice && defaultVoice.voice_id ? String(defaultVoice.voice_id).trim() : '';
            const mappedVoiceId = normalizedSpeakerName && this.state.multiSpeakerMap && this.state.multiSpeakerMap[normalizedSpeakerName]
                ? String(this.state.multiSpeakerMap[normalizedSpeakerName]).trim()
                : '';
            const mappedVoice = mappedVoiceId ? findVoiceById(availableVoices, mappedVoiceId) : null;
            const fallbackVoice = defaultVoiceId ? findVoiceById(availableVoices, defaultVoiceId) : null;
            const selectedVoice = mappedVoice || fallbackVoice || (availableVoices.length > 0 ? availableVoices[0] : null);
            const selectedVoiceId = selectedVoice && selectedVoice.voice_id ? selectedVoice.voice_id : '';
            const selectedGender = selectedVoice && selectedVoice.gender === 'Female' ? 'Female' : 'Male';

            return {
                selectedGender,
                selectedVoice,
                selectedVoiceId,
                voiceOptions: availableVoices.filter((voice) => voice && voice.gender === selectedGender),
            };
        }

        getSingleSpeakerSelection() {
            const availableVoices = this.getAvailableVoiceList();
            const selectedVoiceId = this.state.selectedVoice ? String(this.state.selectedVoice).trim() : '';
            const selectedVoice = selectedVoiceId ? findVoiceById(availableVoices, selectedVoiceId) : null;
            const fallbackVoiceId = selectedVoice && selectedVoice.voice_id
                ? selectedVoice.voice_id
                : this.getDefaultVoiceIdByGender('Male');
            const fallbackVoice = fallbackVoiceId ? findVoiceById(availableVoices, fallbackVoiceId) : null;
            const resolvedVoice = fallbackVoice || selectedVoice || (availableVoices.length > 0 ? availableVoices[0] : null);
            const resolvedVoiceId = resolvedVoice && resolvedVoice.voice_id ? resolvedVoice.voice_id : '';
            const resolvedGender = resolvedVoice && resolvedVoice.gender === 'Female' ? 'Female' : 'Male';

            return {
                selectedGender: resolvedGender,
                selectedVoice: resolvedVoice,
                selectedVoiceId: resolvedVoiceId,
                voiceOptions: availableVoices.filter((voice) => voice && voice.gender === resolvedGender),
            };
        }

        normalizeMultiSpeakerMap(detectedSpeakers, speakerMap) {
            const availableVoices = this.getAvailableVoiceList();
            const sourceMap = speakerMap && typeof speakerMap === 'object' ? speakerMap : {};
            const normalizedMap = {};

            if (!Array.isArray(detectedSpeakers) || detectedSpeakers.length === 0) {
                return normalizedMap;
            }

            detectedSpeakers.forEach((speakerName) => {
                const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
                if (!normalizedSpeakerName) {
                    return;
                }

                const rawSpeakerName = String(speakerName || '').trim();
                const currentVoiceId = sourceMap[normalizedSpeakerName]
                    ? String(sourceMap[normalizedSpeakerName]).trim()
                    : (sourceMap[rawSpeakerName] ? String(sourceMap[rawSpeakerName]).trim() : '');
                const currentVoice = currentVoiceId ? findVoiceById(availableVoices, currentVoiceId) : null;
                if (currentVoice && currentVoice.voice_id) {
                    normalizedMap[normalizedSpeakerName] = currentVoice.voice_id;
                    return;
                }

                const speakerIndex = detectedSpeakers.findIndex((name) => normalizeSpeakerNameKey(name) === normalizedSpeakerName);
                const defaultVoice = availableVoices[Math.max(0, speakerIndex)] || availableVoices[0] || null;
                if (defaultVoice && defaultVoice.voice_id) {
                    normalizedMap[normalizedSpeakerName] = defaultVoice.voice_id;
                }
            });

            return normalizedMap;
        }

        areStringMapsEqual(leftMap, rightMap) {
            const leftObject = leftMap && typeof leftMap === 'object' ? leftMap : {};
            const rightObject = rightMap && typeof rightMap === 'object' ? rightMap : {};
            const leftKeys = Object.keys(leftObject);
            const rightKeys = Object.keys(rightObject);

            if (leftKeys.length !== rightKeys.length) {
                return false;
            }

            for (let index = 0; index < leftKeys.length; index += 1) {
                const key = leftKeys[index];
                if (leftObject[key] !== rightObject[key]) {
                    return false;
                }
            }

            return true;
        }

        handleVoiceSelection(voice) {
            const nextVoiceId = voice.voice_id;
            const nextVoiceGender = voice.gender || this.state.activeVoiceGender;

            this.setState({
                selectedVoice: nextVoiceId,
                activeVoiceGender: nextVoiceGender
            });
            this.props.editPost({ meta: { ...this.props.meta, _Podcastmaker_voice_id: nextVoiceId } });
        }

        handleVoiceGenderChange(nextGender) {
            const normalizedGender = nextGender === 'Female' ? 'Female' : 'Male';
            const nextVoiceId = this.getDefaultVoiceIdByGender(normalizedGender);
            const nextSelectionState = {
                activeVoiceGender: normalizedGender
            };

            if (nextVoiceId) {
                nextSelectionState.selectedVoice = nextVoiceId;
                this.props.editPost({ meta: { ...this.props.meta, _Podcastmaker_voice_id: nextVoiceId } });
            }

            this.setState(nextSelectionState);
        }

        handleTtsModelChange(nextModelId) {
            const normalizedModelId = String(nextModelId || '').trim();
            if (!normalizedModelId) {
                return;
            }

            this.setState({
                selectedTtsModel: normalizedModelId,
            });
        }

        handleModeChange(nextMode) {
            const normalizedMode = normalizeScriptMode(nextMode, this.state.isPro || this.state.isBusiness);
            if (normalizedMode === this.state.scriptMode) {
                return;
            }

            this.stopAllPreview();
            const nextToneStyle = getDefaultSmartToneStyleId(window.PodcastmakerScriptData || {}, normalizedMode);
            const nextPromptSet = getDefaultPromptSetId(window.PodcastmakerScriptData || {}, normalizedMode);
            this.setState({
                scriptMode: normalizedMode,
                selectedToneStyle: nextToneStyle || this.state.selectedToneStyle,
                selectedPrompt: nextPromptSet || this.state.selectedPrompt,
            }, this.refreshSpeakerState);
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_script_mode: normalizedMode,
                    _Podcastmaker_multi_speaker_map: JSON.stringify(this.state.multiSpeakerMap || {}),
                }
            });
        }

        getAvailablePromptSetId(languageCode, scriptMode, preferredPromptId) {
            return resolvePromptSetIdForContext(
                this.state.promptSets,
                this.state.promptSetLookup,
                languageCode,
                scriptMode,
                preferredPromptId
            );
        }

        getActiveContextDraft() {
            const currentPromptSet = this.state.selectedPrompt && this.state.promptSetLookup
                ? this.state.promptSetLookup[this.state.selectedPrompt]
                : null;
            const currentLanguage = this.state.selectedLanguage
                || (currentPromptSet ? getPromptSetLanguage(currentPromptSet) : '')
                || getDefaultPreviewLanguage((window.PodcastmakerScriptData || {}).siteLocale || (window.PodcastmakerScriptData || {}).locale || '');
            const currentLength = normalizeLengthChoice(this.state.selectedLength);
            const currentPromptId = this.getAvailablePromptSetId(currentLanguage, this.state.scriptMode, this.state.selectedPrompt);

            return {
                language: currentLanguage,
                length: currentLength,
                promptId: currentPromptId || this.state.selectedPrompt || '',
                modelId: resolveAvailableModel(this.state.selectedModel || '', this.state.geminiModels || [], ''),
            };
        }

        handleContextLayerOpen() {
            this.setState({
                isContextLayerOpen: true,
                contextDraft: this.getActiveContextDraft(),
            });
        }

        handleContextLayerClose() {
            this.setState({
                isContextLayerOpen: false,
                contextDraft: null,
            });
        }

        handleContextDraftChange(fieldName, nextValue) {
            this.setState((currentState) => {
                const currentDraft = currentState.contextDraft || this.getActiveContextDraft();
                const nextDraft = {
                    ...currentDraft,
                };

                if (fieldName === 'language') {
                    nextDraft.language = normalizeLanguageCode(nextValue);
                    nextDraft.promptId = this.getAvailablePromptSetId(nextDraft.language, this.state.scriptMode, nextDraft.promptId);
                } else if (fieldName === 'length') {
                    nextDraft.length = normalizeLengthChoice(nextValue);
                } else if (fieldName === 'promptId') {
                    nextDraft.promptId = this.getAvailablePromptSetId(nextDraft.language, this.state.scriptMode, nextValue)
                        || nextDraft.promptId;
                } else if (fieldName === 'modelId') {
                    nextDraft.modelId = String(nextValue || '').trim();
                }

                return {
                    contextDraft: nextDraft,
                };
            });
        }

        handleContextLayerApply(onComplete) {
            const currentDraft = this.state.contextDraft || this.getActiveContextDraft();
            const nextLanguage = normalizeLanguageCode(currentDraft.language);
            const nextLength = normalizeLengthChoice(currentDraft.length);
            const nextPromptId = this.getAvailablePromptSetId(nextLanguage, this.state.scriptMode, currentDraft.promptId);
            const nextModelId = resolveAvailableModel(
                currentDraft.modelId || '',
                this.state.geminiModels || [],
                this.state.selectedModel || ''
            );

            this.setState({
                isContextLayerOpen: false,
                contextDraft: null,
                selectedLanguage: nextLanguage,
                selectedLength: nextLength,
                selectedModel: nextModelId,
                selectedPrompt: nextPromptId || currentDraft.promptId || this.state.selectedPrompt,
            }, () => {
                this.refreshSpeakerState();
                if (typeof onComplete === 'function') {
                    onComplete();
                }
            });

            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_script_language: nextLanguage,
                    _Podcastmaker_script_length: nextLength,
                }
            });
        }

        handleContextLayerSelect() {
            this.handleContextLayerApply();
        }

        handleContextLayerCreate() {
            this.handleContextLayerApply(() => {
                this.handleGenerateScript();
            });
        }

        handleVoiceLayerOpen() {
            this.setState({ isVoiceLayerOpen: true });
        }

        handleVoiceLayerClose() {
            this.setState({ isVoiceLayerOpen: false });
        }

        handleVoiceLayerSelect() {
            this.handleVoiceLayerClose();
        }

        handleVoiceLayerCreate() {
            this.handleVoiceLayerClose();
            this.handleGenerateVoice();
        }

        handleSmartToneLayerOpen() {
            this.setState({
                isSmartToneLayerOpen: true,
            });
        }

        handleSmartToneLayerClose() {
            this.setState({ isSmartToneLayerOpen: false });
        }

        handleSmartToneLayerSelect() {
            this.handleSmartToneLayerClose();
        }

        handleSmartToneLayerCreate() {
            this.handleSmartToneLayerClose();
            window.setTimeout(() => {
                this.handleApplySmartTone();
            }, 0);
        }

        handleSpeakerVoiceChange(speakerName, nextVoiceId) {
            const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
            if (!normalizedSpeakerName) {
                return;
            }

            const nextSpeakerMap = {
                ...this.state.multiSpeakerMap,
            };

            if (nextVoiceId) {
                nextSpeakerMap[normalizedSpeakerName] = nextVoiceId;
            } else {
                delete nextSpeakerMap[normalizedSpeakerName];
            }

            this.setState({ multiSpeakerMap: nextSpeakerMap }, this.refreshSpeakerState);
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_multi_speaker_map: JSON.stringify(nextSpeakerMap),
                }
            });
        }

        handleSpeakerVoiceGenderChange(speakerName, nextGender) {
            const normalizedSpeakerName = normalizeSpeakerNameKey(speakerName);
            const normalizedGender = nextGender === 'Female' ? 'Female' : 'Male';
            if (!normalizedSpeakerName) {
                return;
            }

            const nextVoiceId = this.getDefaultVoiceIdByGender(normalizedGender);
            if (!nextVoiceId) {
                return;
            }

            const nextSpeakerMap = {
                ...this.state.multiSpeakerMap,
                [normalizedSpeakerName]: nextVoiceId,
            };

            this.setState({ multiSpeakerMap: nextSpeakerMap }, this.refreshSpeakerState);
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_multi_speaker_map: JSON.stringify(nextSpeakerMap),
                }
            });
        }

        handleToggleVoiceDirection() {
            this.setState((currentState) => ({
                isVoiceDirectionOpen: !currentState.isVoiceDirectionOpen,
            }));
        }

        handleVoiceDirectionChange(nextVoiceDirection) {
            if (!this.state.isPro && !this.state.isBusiness) {
                return;
            }

            const normalizedVoiceDirection = String(nextVoiceDirection || '');
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_voice_direction: normalizedVoiceDirection,
                }
            });
        }

        handleSmartToneStyleChange(nextToneStyle) {
            const normalizedToneStyle = String(nextToneStyle || '').trim();
            if (!normalizedToneStyle || normalizedToneStyle === this.state.selectedToneStyle) {
                return;
            }

            const currentVoiceDirection = this.props.meta && this.props.meta._Podcastmaker_voice_direction
                ? String(this.props.meta._Podcastmaker_voice_direction).trim()
                : '';
            const currentTemplate = getActiveVoiceDirectionTemplate(
                this.state.smartToneStyles,
                this.state.scriptMode,
                this.state.selectedToneStyle
            );
            const nextTemplate = getActiveVoiceDirectionTemplate(
                this.state.smartToneStyles,
                this.state.scriptMode,
                normalizedToneStyle
            );
            const isVoiceDirectionAutoManaged = currentVoiceDirection !== '' && currentVoiceDirection === currentTemplate;

            const applyToneStyleChange = (shouldSyncVoiceDirection) => {
                this.setState({
                    selectedToneStyle: normalizedToneStyle,
                    hasAppliedSmartTone: false,
                });
                this.props.editPost({
                    meta: {
                        ...this.props.meta,
                        _Podcastmaker_smart_tone_applied: '0',
                    }
                });

                if (shouldSyncVoiceDirection && nextTemplate) {
                    this.props.editPost({
                        meta: {
                            ...this.props.meta,
                            _Podcastmaker_voice_direction: nextTemplate,
                        }
                    });
                }
            };

            if (isVoiceDirectionAutoManaged) {
                applyToneStyleChange(true);
                return;
            }

            this.setState({
                pendingSmartToneStyleChange: {
                    nextToneStyle: normalizedToneStyle,
                    nextTemplate,
                    currentTemplate,
                }
            });
        }

        confirmSmartToneStyleChange() {
            const pendingSmartToneStyleChange = this.state.pendingSmartToneStyleChange;
            if (!pendingSmartToneStyleChange) {
                return;
            }

            const nextToneStyle = pendingSmartToneStyleChange.nextToneStyle || '';
            const nextTemplate = pendingSmartToneStyleChange.nextTemplate || '';
            if (!nextToneStyle) {
                this.setState({ pendingSmartToneStyleChange: null });
                return;
            }

            this.setState({
                selectedToneStyle: nextToneStyle,
                pendingSmartToneStyleChange: null,
                hasAppliedSmartTone: false,
            });
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_smart_tone_applied: '0',
                }
            });

            if (nextTemplate) {
                this.props.editPost({
                    meta: {
                        ...this.props.meta,
                        _Podcastmaker_voice_direction: nextTemplate,
                    }
                });
            }
        }

        cancelSmartToneStyleChange() {
            if (!this.state.pendingSmartToneStyleChange) {
                return;
            }

            this.setState({ pendingSmartToneStyleChange: null });
        }

        handleTogglePayloadPreview(event) {
            const nextValue = !!(event && event.target && event.target.checked);
            this.setState({ showPayloadPreview: nextValue });
        }

        async handleCopyPayloadPreview(payloadPreview) {
            const payloadText = JSON.stringify(payloadPreview, null, 2);

            try {
                if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
                    await navigator.clipboard.writeText(payloadText);
                } else {
                    const temporaryTextarea = document.createElement('textarea');
                    temporaryTextarea.value = payloadText;
                    temporaryTextarea.setAttribute('readonly', 'readonly');
                    temporaryTextarea.style.position = 'absolute';
                    temporaryTextarea.style.left = '-9999px';
                    document.body.appendChild(temporaryTextarea);
                    temporaryTextarea.select();
                    document.execCommand('copy');
                    document.body.removeChild(temporaryTextarea);
                }

                this.setState({ payloadCopyStatus: 'Copied.' });
                window.setTimeout(() => {
                    this.setState({ payloadCopyStatus: '' });
                }, 2000);
            } catch (error) {
                this.setState({ payloadCopyStatus: 'Copy failed.' });
            }
        }

        async fetchAjaxJson(formData, stageLabel) {
            const response = await fetch(window.PodcastmakerScriptData.ajaxUrl, { method: 'POST', body: formData });
            const responseText = await response.text();

            try {
                return JSON.parse(responseText);
            } catch (parseError) {
                const excerpt = typeof responseText === 'string'
                    ? responseText.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 180)
                    : '';
                const stagePrefix = stageLabel ? `${stageLabel}: ` : '';
                if (excerpt) {
                    throw new Error(`${stagePrefix}Server returned HTML instead of JSON. ${excerpt}`);
                }

                throw new Error(`${stagePrefix}Server returned an invalid JSON response.`);
            }
        }

        clearGenerationProgressTimers() {
            if (this.generationProgressTimer) {
                window.clearInterval(this.generationProgressTimer);
                this.generationProgressTimer = null;
            }

            if (this.generationProgressCloseTimer) {
                window.clearTimeout(this.generationProgressCloseTimer);
                this.generationProgressCloseTimer = null;
            }
        }

        startGenerationProgress(operationKey, options) {
            const generationEstimate = buildGenerationEstimate(operationKey, options || {});

            this.clearGenerationProgressTimers();
            this.generationProgressStartedAt = Date.now();
            this.generationProgressPhaseStartedAt = this.generationProgressStartedAt;
            this.generationProgressEstimatedSeconds = generationEstimate.totalSeconds;

            this.setState({
                generationProgress: {
                    visible: true,
                    status: 'running',
                    operationKey,
                    title: generationEstimate.title,
                    stage: 'Queued...',
                    detail: generationEstimate.detail,
                    rangeLabel: generationEstimate.rangeLabel,
                    phaseLabel: 'Queued',
                    progressPercent: 0,
                    remainingLabel: formatGenerationRemainingLabel(generationEstimate.totalSeconds),
                    currentChunk: 0,
                    completedChunks: 0,
                    totalChunks: Number(options && options.chunkCount) || 0,
                    isChunkBased: generationEstimate.isChunkBased,
                    phaseType: 'queued',
                }
            });

            this.generationProgressTimer = window.setInterval(this.updateGenerationProgress, 350);
        }

        updateGenerationProgress() {
            const generationProgress = this.state.generationProgress;
            if (!generationProgress || !generationProgress.visible || generationProgress.status !== 'running') {
                return;
            }

            const estimatedSeconds = Math.max(1, Number(this.generationProgressEstimatedSeconds) || 1);
            const elapsedSeconds = Math.max(0, (Date.now() - this.generationProgressStartedAt) / 1000);

            let progressPercent = clampNumber(Math.round((elapsedSeconds / estimatedSeconds) * 100), 0, 92);
            let phaseLabel = getGenerationPhaseLabel(progressPercent);
            let stage = generationProgress.stage;
            const totalChunks = Number(generationProgress.totalChunks) || 0;
            const currentChunk = Number(generationProgress.currentChunk) || 0;
            const completedChunks = Number(generationProgress.completedChunks) || 0;
            const phaseElapsedSeconds = Math.max(0, (Date.now() - this.generationProgressPhaseStartedAt) / 1000);
            let remainingLabel = progressPercent >= 100
                ? 'Complete'
                : formatGenerationRemainingLabel(estimatedSeconds - elapsedSeconds);

            if (generationProgress.operationKey === 'generate-voice' && generationProgress.isChunkBased) {
                const activePhaseType = generationProgress.phaseType || 'queued';

                if (activePhaseType === 'prepare') {
                    const prepareRatio = clampNumber(phaseElapsedSeconds / this.generationProgressPrepareEstimatedSeconds, 0, 0.95);
                    progressPercent = Math.max(2, Math.round(prepareRatio * 10));
                    phaseLabel = 'Starting';
                    remainingLabel = formatGenerationRemainingLabel(
                        Math.max(1, estimatedSeconds - elapsedSeconds)
                    );
                } else if (activePhaseType === 'chunk' && totalChunks > 0) {
                    const currentChunkRatio = clampNumber(phaseElapsedSeconds / this.generationProgressChunkEstimatedSeconds, 0, 0.9);
                    const chunkProgress = (completedChunks + currentChunkRatio) / totalChunks;
                    const remainingChunkUnits = Math.max(0, totalChunks - completedChunks - currentChunkRatio);
                    const remainingChunkSeconds = remainingChunkUnits * this.generationProgressChunkEstimatedSeconds;
                    progressPercent = clampNumber(Math.round(10 + (chunkProgress * 75)), 10, 84);
                    phaseLabel = 'Processing';
                    remainingLabel = formatGenerationRemainingLabel(
                        remainingChunkSeconds + this.generationProgressFinalizeEstimatedSeconds
                    );
                } else if (activePhaseType === 'finalize') {
                    const finalizeRatio = clampNumber(phaseElapsedSeconds / this.generationProgressFinalizeEstimatedSeconds, 0, 0.95);
                    progressPercent = clampNumber(Math.round(85 + (finalizeRatio * 10)), 85, 95);
                    phaseLabel = 'Finalizing';
                    remainingLabel = formatGenerationRemainingLabel(
                        Math.max(1, this.generationProgressFinalizeEstimatedSeconds * (1 - finalizeRatio))
                    );
                }
            } else if (generationProgress.isChunkBased && totalChunks > 0) {
                const chunkPercent = clampNumber(Math.round((currentChunk / totalChunks) * 100), 0, 100);
                progressPercent = Math.max(progressPercent, chunkPercent);
            }

            if (generationProgress.status === 'complete') {
                progressPercent = 100;
                phaseLabel = 'Done';
            }

            this.setState({
                generationProgress: {
                    ...generationProgress,
                    progressPercent,
                    phaseLabel,
                    stage,
                    remainingLabel,
                }
            });
        }

        finishGenerationProgress(successStage) {
            const generationProgress = this.state.generationProgress;
            if (!generationProgress) {
                return;
            }

            this.clearGenerationProgressTimers();

            this.setState({
                generationProgress: {
                    ...generationProgress,
                    visible: true,
                    status: 'complete',
                    stage: successStage || 'Complete',
                    phaseLabel: 'Done',
                    progressPercent: 100,
                    remainingLabel: 'Complete',
                }
            });

            this.generationProgressCloseTimer = window.setTimeout(() => {
                this.setState({ generationProgress: null });
                this.clearGenerationProgressTimers();
            }, 900);
        }

        failGenerationProgress(errorMessage) {
            const generationProgress = this.state.generationProgress;
            if (!generationProgress) {
                return;
            }

            this.clearGenerationProgressTimers();

            this.setState({
                generationProgress: {
                    ...generationProgress,
                    visible: true,
                    status: 'error',
                    stage: errorMessage || 'Generation failed',
                    phaseLabel: 'Error',
                    progressPercent: 100,
                    remainingLabel: 'Stopped',
                }
            });

            this.generationProgressCloseTimer = window.setTimeout(() => {
                this.setState({ generationProgress: null });
                this.clearGenerationProgressTimers();
            }, 1400);
        }

        closeGenerationProgress() {
            this.clearGenerationProgressTimers();
            this.setState({ generationProgress: null });
        }

        renderGenerationProgressPopup(isInline = false) {
            const generationProgress = this.state.generationProgress;
            if (!generationProgress || !generationProgress.visible) {
                return null;
            }

            const popupClassName = [
                'pm-generation-popup',
                isInline ? 'pm-generation-popup--inline' : '',
                generationProgress.status === 'error' ? 'is-error' : '',
                generationProgress.status === 'complete' ? 'is-complete' : '',
            ].filter(Boolean).join(' ');

            return createElement('div', { className: popupClassName },
                createElement('div', { className: 'pm-generation-popup__head' },
                    createElement('div', { className: 'pm-generation-popup__heading' },
                        createElement('span', { className: 'pm-generation-popup__eyebrow' }, __('Generation status', 'quikvox-ai')),
                        createElement('strong', null, generationProgress.title || __('Processing', 'quikvox-ai'))
                    )
                ),
                createElement('div', { className: 'pm-generation-popup__status' },
                    createElement('span', {
                        className: `pm-generation-popup__status-text ${generationProgress.status === 'error' ? 'is-error' : generationProgress.status === 'complete' ? 'is-success' : ''}`
                    }, generationProgress.remainingLabel || __('Working...', 'quikvox-ai')),
                    createElement('span', { className: 'pm-generation-popup__status-percent' }, `${clampNumber(Number(generationProgress.progressPercent) || 0, 0, 100)}%`)
                ),
                createElement('div', { className: 'pm-generation-popup__bar' },
                    createElement('span', {
                        style: {
                            width: `${clampNumber(Number(generationProgress.progressPercent) || 0, 0, 100)}%`
                        }
                    })
                )
            );
        }

        renderWorkflowSidebar() {
            const activeScriptMode = normalizeScriptMode(this.state.scriptMode, this.state.isPro || this.state.isBusiness);
            const isMultiSpeakerMode = activeScriptMode === 'multi';
            const hasScriptContent = getScriptPlainTextFromBlocks(this.props.blocks).trim().length > 0;
            const isScriptGenerationComplete = hasScriptContent && !this.state.isGeneratingScript;
            const filteredToneStyles = getFilteredSmartToneStyles(this.state.smartToneStyles, activeScriptMode);
            const activeToneStyle = filteredToneStyles.some((option) => option.value === this.state.selectedToneStyle)
                ? this.state.selectedToneStyle
                : (filteredToneStyles[0] ? filteredToneStyles[0].value : '');
            const activeToneStyleDefinition = filteredToneStyles.find((option) => option.value === activeToneStyle) || null;
            const isSmartToneComplete = !!this.state.hasAppliedSmartTone && !this.state.isApplyingSmartTone;
            const selectedVoiceSummaryLabel = normalizeVoiceSummaryLabel(this.state.generatedVoiceSummaryLabel || buildVoiceSummaryLabel(
                activeScriptMode,
                this.state.selectedVoice,
                this.state.multiSpeakerMap,
                this.state.detectedSpeakers
            ));
            const createVoiceLabel = selectedVoiceSummaryLabel
                ? selectedVoiceSummaryLabel
                : (activeScriptMode === 'multi' ? __('Speaker mapping not selected', 'quikvox-ai') : __('Speaker 1 not selected', 'quikvox-ai'));
            const hasGeneratedAudio = !!this.state.generatedAudioUrl;
            const hasVoiceSelection = !!selectedVoiceSummaryLabel;
            const createVoiceBodyClassName = `step-item ${hasGeneratedAudio ? 'done' : 'current'} pm-sidebar-step-card pm-sidebar-create-voice-card${hasGeneratedAudio ? ' has-audio' : ''}`;
            const createVoiceStepStatus = hasGeneratedAudio
                ? __('Voice is ready. Preview and download are available below.', 'quikvox-ai')
                : hasVoiceSelection
                    ? __('Voice is selected. Create Voice will generate audio from this setup.', 'quikvox-ai')
                    : __('Open the voice layer to choose a voice and generate audio.', 'quikvox-ai');

            const speakerCards = isMultiSpeakerMode
                ? (this.state.detectedSpeakers.length > 0
                    ? this.state.detectedSpeakers.map((speakerName, index) => {
                        const speakerSelection = this.getMultiSpeakerSelection(speakerName);
                        const mappedVoice = speakerSelection.selectedVoice;
                        const mappedGender = speakerSelection.selectedGender || 'Male';
                        const selectedVoiceName = mappedVoice ? mappedVoice.voice_id : __('No voice selected', 'quikvox-ai');
                        const voiceMeta = mappedVoice && mappedVoice.characteristics
                            ? mappedVoice.characteristics
                            : sprintf(__('%s filter', 'quikvox-ai'), mappedGender);

                        return createElement('div', {
                            key: speakerName,
                            className: 'pm-sidebar-voice-card pm-sidebar-voice-card--selectable',
                            role: 'button',
                            tabIndex: 0,
                            onClick: this.handleVoiceLayerOpen,
                            onKeyDown: (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleVoiceLayerOpen();
                                }
                            }
                        },
                            createElement('div', { className: 'pm-sidebar-voice-card__head' },
                                createElement('strong', null, sprintf(__('Speaker %d', 'quikvox-ai'), index + 1)),
                                createElement('span', null, __('Open voice modal', 'quikvox-ai'))
                            ),
                            createElement('div', { className: 'pm-sidebar-voice-card__body' },
                                createElement('div', { className: 'pm-sidebar-voice-card__row' },
                                    createElement('span', { className: 'label' }, __('Voice', 'quikvox-ai')),
                                    createElement('span', { className: 'value' }, sprintf(__('%s filter', 'quikvox-ai'), mappedGender))
                                ),
                                createElement('div', { className: 'pm-sidebar-voice-card__picker' },
                                    createElement('div', { className: 'left' },
                                        createElement('span', { className: `gender-chip ${mappedGender === 'Female' ? 'female' : 'male'}` }, mappedGender === 'Female' ? '♀' : '♂'),
                                        mappedVoice && voiceMeta
                                        ? sprintf(__('%1$s - %2$s', 'quikvox-ai'), selectedVoiceName, voiceMeta)
                                            : sprintf(__('%s', 'quikvox-ai'), selectedVoiceName)
                                    ),
                                    createElement('span', null, __('Open', 'quikvox-ai'))
                                ),
                                createElement('div', { className: 'chips' },
                                    createElement('span', { className: 'chip done' }, __('✓ Selected', 'quikvox-ai')),
                                    createElement('span', { className: 'chip optional' }, index === 0 ? __('Current setup', 'quikvox-ai') : __('Optional', 'quikvox-ai'))
                                )
                            )
                        );
                    })
                    : [
                        createElement('div', {
                            key: 'empty-speakers',
                            className: 'pm-sidebar-voice-card pm-sidebar-voice-card--empty pm-sidebar-voice-card--selectable',
                            role: 'button',
                            tabIndex: 0,
                            onClick: this.handleVoiceLayerOpen,
                            onKeyDown: (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleVoiceLayerOpen();
                                }
                            }
                        },
                        createElement('div', { className: 'pm-sidebar-voice-card__head' },
                            createElement('strong', null, __('Speaker 1', 'quikvox-ai')),
                            createElement('span', null, __('Open voice modal', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-sidebar-voice-card__body' },
                            createElement('div', { className: 'pm-sidebar-voice-card__row' },
                                createElement('span', { className: 'label' }, __('Voice', 'quikvox-ai')),
                                createElement('span', { className: 'value' }, __('Male filter', 'quikvox-ai'))
                            ),
                            createElement('div', { className: 'pm-sidebar-voice-card__picker' },
                                createElement('div', { className: 'left' },
                                    createElement('span', { className: 'gender-chip male' }, '♂'),
                                    __('First available voice', 'quikvox-ai')
                                ),
                                createElement('span', null, __('Open', 'quikvox-ai'))
                            ),
                            createElement('div', { className: 'chips' },
                                createElement('span', { className: 'chip done' }, __('✓ Selected', 'quikvox-ai')),
                                createElement('span', { className: 'chip optional' }, __('Optional', 'quikvox-ai'))
                            )
                        )
                    )
                ])
                : [
                    createElement('div', {
                        key: 'single-speaker',
                        className: 'pm-sidebar-voice-card pm-sidebar-voice-card--selectable',
                        role: 'button',
                        tabIndex: 0,
                        onClick: this.handleVoiceLayerOpen,
                        onKeyDown: (event) => {
                            if (event.key === 'Enter' || event.key === ' ') {
                                event.preventDefault();
                                this.handleVoiceLayerOpen();
                            }
                        }
                    },
                        createElement('div', { className: 'pm-sidebar-voice-card__head' },
                            createElement('strong', null, __('Speaker 1', 'quikvox-ai')),
                            createElement('span', null, __('Open voice modal', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-sidebar-voice-card__body' },
                            createElement('div', { className: 'pm-sidebar-voice-card__row' },
                                createElement('span', { className: 'label' }, __('Voice', 'quikvox-ai')),
                                createElement('span', { className: 'value' }, sprintf(__('%s filter', 'quikvox-ai'), this.state.activeVoiceGender || __('Male', 'quikvox-ai')))
                            ),
                            createElement('div', { className: 'pm-sidebar-voice-card__picker' },
                                createElement('div', { className: 'left' },
                                    createElement('span', { className: `gender-chip ${(this.state.activeVoiceGender || 'Male') === 'Female' ? 'female' : 'male'}` }, (this.state.activeVoiceGender || 'Male') === 'Female' ? '♀' : '♂'),
                                    this.state.selectedVoice
                                        ? sprintf(__(' %s', 'quikvox-ai'), this.state.selectedVoice)
                                        : __('First available voice', 'quikvox-ai')
                                ),
                                createElement('span', null, __('Open', 'quikvox-ai'))
                            ),
                            createElement('div', { className: 'chips' },
                                createElement('span', { className: 'chip done' }, __('✓ Selected', 'quikvox-ai')),
                                createElement('span', { className: 'chip optional' }, __('Optional', 'quikvox-ai'))
                            )
                        )
                    )
                ];

            return createElement('div', { className: 'pm-sidebar-shell' },
                createElement('div', { className: 'pm-sidebar-card' },
                    createElement('div', { className: 'sidebar-head' },
                        createElement('div', { className: 'type-switch' },
                            createElement('div', {
                                className: `type-chip ${activeScriptMode === 'single' ? 'active' : ''}`,
                                onClick: () => this.handleModeChange('single'),
                                role: 'button',
                                tabIndex: 0
                            }, __('Single Speaker', 'quikvox-ai')),
                            createElement('div', {
                                className: `type-chip ${activeScriptMode === 'multi' ? 'active' : ''}`,
                                onClick: () => this.handleModeChange('multi'),
                                role: 'button',
                                tabIndex: 0
                            }, __('Multi Speaker', 'quikvox-ai'))
                        )
                    ),
                    createElement('div', { className: 'step-stack' },
                        createElement('div', { className: 'step-item done' },
                            createElement('div', { className: 'step-icon' }, '✓'),
                            createElement('div', null,
                                createElement('div', { className: 'step-title' }, __('1. Speaker Type', 'quikvox-ai')),
                                createElement('div', { className: 'step-sub' }, __('The first and only direct choice here. Switch between Single and Multi.', 'quikvox-ai'))
                            )
                        ),
                        createElement('div', {
                            className: `step-item ${isScriptGenerationComplete ? 'done' : 'optional'} pm-sidebar-step-card`,
                            role: 'button',
                            tabIndex: 0,
                            onClick: this.handleContextLayerOpen,
                            onKeyDown: (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleContextLayerOpen();
                                }
                            }
                        },
                            createElement('div', { className: 'step-icon' }, isScriptGenerationComplete ? '✓' : ''),
                            createElement('div', null,
                                createElement('div', { className: 'step-title' }, __('2. Generate Script', 'quikvox-ai')),
                                createElement('div', { className: 'step-sub' }, isScriptGenerationComplete ? __('Completed. The script is already in the editor.', 'quikvox-ai') : __('Optional. Open the layer when you want to configure or generate.', 'quikvox-ai'))
                            )
                        ),
                        createElement('div', {
                            className: `step-item ${isSmartToneComplete ? 'done' : this.state.isApplyingSmartTone ? 'current' : 'optional'} pm-sidebar-step-card`,
                            role: 'button',
                            tabIndex: 0,
                            onClick: this.handleSmartToneLayerOpen,
                            onKeyDown: (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleSmartToneLayerOpen();
                                }
                            }
                        },
                            createElement('div', { className: 'step-icon' }, isSmartToneComplete ? '✓' : ''),
                            createElement('div', null,
                                createElement('div', { className: 'step-title' }, __('3. Smart Tone', 'quikvox-ai')),
                                createElement('div', { className: 'step-sub' }, isSmartToneComplete ? __('Completed. The tone has been applied to the script.', 'quikvox-ai') : this.state.isApplyingSmartTone ? __('Current action. The tone is being applied.', 'quikvox-ai') : __('Optional. Use the layer only when tone refinement is needed.', 'quikvox-ai'))
                            )
                        ),
                        createElement('div', {
                            className: createVoiceBodyClassName,
                            role: 'button',
                            tabIndex: 0,
                            onClick: this.handleVoiceLayerOpen,
                            onKeyDown: (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleVoiceLayerOpen();
                                }
                            }
                        },
                            createElement('div', { className: 'step-icon' }, hasGeneratedAudio ? '✓' : ''),
                            createElement('div', null,
                                createElement('div', { className: 'step-title' }, __('4. Create Voice', 'quikvox-ai')),
                                createElement('div', { className: 'step-sub' }, createVoiceStepStatus),
                                createElement('div', { className: 'pm-sidebar-voice-summary' },
                                    createElement('span', { className: 'pm-sidebar-voice-summary__value' }, createVoiceLabel)
                                )
                            )
                        ),
                        hasGeneratedAudio ? createElement('div', { className: 'pm-sidebar-voice-preview' },
                            createElement('div', { className: 'pm-sidebar-voice-preview__head' },
                                createElement('strong', null, __('Preview', 'quikvox-ai')),
                                createElement('a', {
                                    href: this.state.generatedAudioUrl,
                                    download: '',
                                    target: '_blank',
                                    className: 'pm-sidebar-voice-preview__link'
                                }, __('Download WAV', 'quikvox-ai'))
                            ),
                            createElement('audio', {
                                controls: true,
                                src: this.state.generatedAudioUrl,
                                className: 'pm-sidebar-voice-preview__player'
                            }),
                        ) : null,
                        createElement('div', {
                            className: `step-item ${this.state.isEmbedded ? 'done' : hasGeneratedAudio ? 'current' : 'locked'} pm-sidebar-step-card pm-sidebar-embed-step-card`,
                            role: hasGeneratedAudio && !this.state.isEmbedded ? 'button' : undefined,
                            tabIndex: hasGeneratedAudio && !this.state.isEmbedded ? 0 : undefined,
                            onClick: hasGeneratedAudio && !this.state.isEmbedded ? this.handleInsertToSource : undefined,
                            onKeyDown: hasGeneratedAudio && !this.state.isEmbedded ? (event) => {
                                if (event.key === 'Enter' || event.key === ' ') {
                                    event.preventDefault();
                                    this.handleInsertToSource();
                                }
                            } : undefined
                        },
                            createElement('div', { className: 'step-icon' }, this.state.isEmbedded ? '✓' : hasGeneratedAudio ? '↗' : '🔒'),
                            createElement('div', null,
                                createElement('div', { className: 'step-title' }, __('5. Embed in Article', 'quikvox-ai')),
                                createElement('div', { className: 'step-sub' }, this.state.isEmbedded
                                    ? __('Audio embedded into the article.', 'quikvox-ai')
                                    : hasGeneratedAudio
                                        ? __('Click to embed the generated audio into the article.', 'quikvox-ai')
                                        : __('Locked until audio is created.', 'quikvox-ai'))
                            )
                        ),
                        this.renderGenerationProgressPopup(true)
                    ),
                )
            );
        }

        async handleMultiSpeakerVoiceGeneration(plainText) {
            const voiceDirectionValue = this.props.meta && this.props.meta._Podcastmaker_voice_direction
                ? String(this.props.meta._Podcastmaker_voice_direction)
                : '';
            const initialVoiceEstimate = buildGenerationEstimate('generate-voice', {
                textLength: plainText.length,
                chunkCount: 0,
            });
            this.generationProgressPrepareEstimatedSeconds = 8;
            this.generationProgressPhaseStartedAt = Date.now();
            this.startGenerationProgress('generate-voice', {
                textLength: plainText.length,
                chunkCount: 0,
            });

            const prepareData = new FormData();
            prepareData.append('action', 'Podcastmaker_prepare_multi_speaker_voice');
            prepareData.append('text', plainText);
            prepareData.append('script_mode', 'multi');
            prepareData.append('speaker_map_json', JSON.stringify(this.state.multiSpeakerMap || {}));
            prepareData.append('tts_model_id', this.state.selectedTtsModel);
            prepareData.append('post_id', this.props.postId);
            prepareData.append('voice_direction', voiceDirectionValue);
            prepareData.append('nonce', window.PodcastmakerScriptData.nonce);

            this.setState({
                isGeneratingVoice: true,
                generationProgress: {
                    visible: true,
                    status: 'running',
                    operationKey: 'generate-voice',
                    title: __('Generate Voice', 'quikvox-ai'),
                    stage: __('Preparing chunk plan...', 'quikvox-ai'),
                    detail: __('The voice workflow is preparing the chunk plan before synthesis starts.', 'quikvox-ai'),
                    rangeLabel: initialVoiceEstimate.rangeLabel,
                    phaseLabel: __('Starting', 'quikvox-ai'),
                    progressPercent: 0,
                    remainingLabel: formatGenerationRemainingLabel(initialVoiceEstimate.totalSeconds),
                    currentChunk: 0,
                    completedChunks: 0,
                    totalChunks: 0,
                    isChunkBased: true,
                    phaseType: 'prepare',
                }
            });

            const prepareJson = await this.fetchAjaxJson(prepareData, __('Preparing chunk plan', 'quikvox-ai'));
            if (!prepareJson.success) {
                throw new Error(prepareJson.data && prepareJson.data.message ? prepareJson.data.message : __('Failed to prepare multi-speaker generation.', 'quikvox-ai'));
            }

            const sessionId = prepareJson.data.session_id;
            const totalChunks = Number(prepareJson.data.total_chunks) || 0;
            const voiceChunkEstimate = buildGenerationEstimate('generate-voice', {
                textLength: plainText.length,
                chunkCount: totalChunks,
            });
            this.generationProgressChunkEstimatedSeconds = getEstimatedVoiceChunkSeconds(plainText.length, totalChunks);
            this.generationProgressFinalizeEstimatedSeconds = getEstimatedVoiceFinalizeSeconds(totalChunks);
            this.generationProgressEstimatedSeconds = this.generationProgressPrepareEstimatedSeconds
                + (this.generationProgressChunkEstimatedSeconds * Math.max(1, totalChunks))
                + this.generationProgressFinalizeEstimatedSeconds;

            for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex += 1) {
                this.generationProgressPhaseStartedAt = Date.now();
                this.setState({
                    generationProgress: {
                        visible: true,
                        status: 'running',
                        operationKey: 'generate-voice',
                        title: __('Generate Voice', 'quikvox-ai'),
                        stage: sprintf(__('Generating chunk %1$d/%2$d...', 'quikvox-ai'), chunkIndex + 1, totalChunks),
                        detail: sprintf(__('%1$d chunk%2$s are being processed sequentially.', 'quikvox-ai'), totalChunks, totalChunks === 1 ? '' : 's'),
                        rangeLabel: voiceChunkEstimate.rangeLabel,
                        phaseLabel: __('Processing', 'quikvox-ai'),
                        progressPercent: totalChunks > 0 ? Math.min(84, Math.round(10 + ((chunkIndex / totalChunks) * 75))) : 40,
                        remainingLabel: formatGenerationRemainingLabel(
                            ((totalChunks - chunkIndex) * this.generationProgressChunkEstimatedSeconds)
                            + this.generationProgressFinalizeEstimatedSeconds
                        ),
                        currentChunk: chunkIndex + 1,
                        completedChunks: chunkIndex,
                        totalChunks,
                        isChunkBased: true,
                        phaseType: 'chunk',
                    }
                });

                const chunkData = new FormData();
                chunkData.append('action', 'Podcastmaker_generate_multi_speaker_chunk');
                chunkData.append('session_id', sessionId);
                chunkData.append('chunk_index', String(chunkIndex));
                chunkData.append('nonce', window.PodcastmakerScriptData.nonce);

                const chunkJson = await this.fetchAjaxJson(chunkData, sprintf(__('Generating chunk %1$d/%2$d', 'quikvox-ai'), chunkIndex + 1, totalChunks));
                if (!chunkJson.success) {
                    throw new Error(chunkJson.data && chunkJson.data.message ? chunkJson.data.message : sprintf(__('Chunk %d failed.', 'quikvox-ai'), chunkIndex + 1));
                }
            }

            this.generationProgressPhaseStartedAt = Date.now();
            this.setState({
                generationProgress: {
                    visible: true,
                    status: 'running',
                    operationKey: 'generate-voice',
                    title: __('Generate Voice', 'quikvox-ai'),
                    stage: __('Combining audio...', 'quikvox-ai'),
                    detail: __('All chunks are complete and the final audio is being combined.', 'quikvox-ai'),
                    rangeLabel: voiceChunkEstimate.rangeLabel,
                    phaseLabel: __('Finalizing', 'quikvox-ai'),
                    progressPercent: 85,
                    remainingLabel: formatGenerationRemainingLabel(this.generationProgressFinalizeEstimatedSeconds),
                    currentChunk: totalChunks,
                    completedChunks: totalChunks,
                    totalChunks,
                    isChunkBased: true,
                    phaseType: 'finalize',
                }
            });

            const finalizeData = new FormData();
            finalizeData.append('action', 'Podcastmaker_finalize_multi_speaker_voice');
            finalizeData.append('session_id', sessionId);
            finalizeData.append('nonce', window.PodcastmakerScriptData.nonce);

            const finalizeJson = await this.fetchAjaxJson(finalizeData, __('Combining audio', 'quikvox-ai'));
            if (!finalizeJson.success) {
                throw new Error(finalizeJson.data && finalizeJson.data.message ? finalizeJson.data.message : __('Failed to finalize multi-speaker audio.', 'quikvox-ai'));
            }

            const returnedSpeakerMap = parseSpeakerMapJson(finalizeJson.data.speaker_map_json);
            const generatedSummaryLabel = finalizeJson.data.voice_summary_label || buildVoiceSummaryLabel(
                'multi',
                '',
                returnedSpeakerMap,
                this.state.detectedSpeakers
            );

            this.setState({
                generatedAudioUrl: finalizeJson.data.audio_url,
                generatedAttachId: finalizeJson.data.attach_id || 0,
                generatedVoiceSummaryLabel: generatedSummaryLabel,
                multiSpeakerMap: returnedSpeakerMap,
                isGeneratingVoice: false,
            });
            this.finishGenerationProgress(__('Voice generated.', 'quikvox-ai'));
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_audio_url: finalizeJson.data.audio_url,
                    _Podcastmaker_audio_id: finalizeJson.data.attach_id || 0,
                    _Podcastmaker_audio_embedded: '0',
                    _Podcastmaker_script_mode: 'multi',
                    _Podcastmaker_voice_id: '',
                    _Podcastmaker_voice_summary_label: generatedSummaryLabel,
                    _Podcastmaker_multi_speaker_map: JSON.stringify(returnedSpeakerMap),
                }
            });
        }

        maybeApplyVoiceDirectionTemplateAfterScriptGeneration() {
            if (this.state.scriptMode !== 'multi') {
                return;
            }

            const currentVoiceDirection = this.props.meta && this.props.meta._Podcastmaker_voice_direction
                ? String(this.props.meta._Podcastmaker_voice_direction).trim()
                : '';
            if (currentVoiceDirection === '') {
                return;
            }

            const activeStyle = (this.state.smartToneStyles || []).find((option) => option.value === this.state.selectedToneStyle);
            const templateText = activeStyle && activeStyle.voice_direction_template ? String(activeStyle.voice_direction_template).trim() : '';
            if (!templateText) {
                return;
            }

            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_voice_direction: templateText,
                }
            });
        }

        togglePreview(id, e) {
            if (e) e.stopPropagation();

            if (this.state.playingVoice === id) {
                this.stopAllPreview();
                return;
            }

            this.stopAllPreview();

            const audioPath = `${this.state.assetsUrl}assets/audio/previews/${this.state.previewLang}/${id}_${this.state.previewLang}.mp3`;
            this.currentAudio = new Audio(audioPath);

            this.setState({ playingVoice: id });

            this.currentAudio.play().catch(err => {
                console.error("Playback failed:", err);
                this.stopAllPreview();
            });

            this.currentAudio.onended = () => {
                this.stopAllPreview();
            };
        }

        stopAllPreview() {
            if (this.currentAudio) {
                this.currentAudio.pause();
                this.currentAudio = null;
            }
            this.setState({ playingVoice: null });
        }

        fetchSourcePosts() {
            const urlParams = new URLSearchParams(window.location.search);
            const preId = urlParams.get('source_post_id');

            // Fetch both posts and pages
            Promise.all([
                wp.apiFetch({ path: '/wp/v2/posts?per_page=20' }).catch(() => []),
                wp.apiFetch({ path: '/wp/v2/pages?per_page=20' }).catch(() => [])
            ]).then(([posts, pages]) => {
                // Combine posts and pages
                const postOptions = posts.map(post => ({ label: `[Post] ${post.title.rendered}`, value: post.id }));
                const pageOptions = pages.map(page => ({ label: `[Page] ${page.title.rendered}`, value: page.id }));
                const allOptions = [...postOptions, ...pageOptions];

                this.setState({ sourcePosts: [{ label: __('Select Source Article', 'quikvox-ai'), value: '' }, ...allOptions] });

                if (preId) {
                    this.setState({ sourcePostId: preId });
                    const currentTitle = wp.data.select('core/editor').getEditedPostAttribute('title');
                    const editData = { meta: { ...this.props.meta, _Podcastmaker_source_post_id: preId } };

                    // Fetch source post/page title from API if current title is empty
                    if (!currentTitle || currentTitle === '(no title)') {
                        // Try posts first, then pages
                        wp.apiFetch({ path: `/wp/v2/posts/${preId}` }).then(post => {
                            editData.title = post.title.rendered || __('Talk Script', 'quikvox-ai');
                            this.props.editPost(editData);
                        }).catch(err => {
                            // Try as page if post fails
                            wp.apiFetch({ path: `/wp/v2/pages/${preId}` }).then(page => {
                                editData.title = page.title.rendered || __('Talk Script', 'quikvox-ai');
                                this.props.editPost(editData);
                            }).catch(err2 => {
                                console.error('Failed to fetch source post/page:', err2);
                                editData.title = __('Talk Script', 'quikvox-ai');
                                this.props.editPost(editData);
                            });
                        });
                    } else {
                        this.props.editPost(editData);
                    }
                }
            });
        }

        async handleGenerateScript() {
            const { selectedPrompt, sourcePostId, selectedModel, selectedLanguage, selectedLength } = this.state;
            if (!selectedPrompt || !sourcePostId) return alert(__('Select Source Article and Prompt Set.', 'quikvox-ai'));

            this.startGenerationProgress('generate-script', {
                textLength: stripHtmlTags(this.state.sourceContent).length,
            });
            this.setState({
                isGeneratingScript: true,
                hasAppliedSmartTone: false,
            });
            this.props.editPost({
                meta: {
                    ...this.props.meta,
                    _Podcastmaker_smart_tone_applied: '0',
                }
            });

            const formData = new FormData();
            formData.append('action', 'Podcastmaker_generate_script');
            formData.append('prompt_id', selectedPrompt);
            formData.append('source_post_id', sourcePostId);
            formData.append('post_id', this.props.postId || 0);
            formData.append('script_language', normalizeLanguageCode(selectedLanguage));
            formData.append('script_length', normalizeLengthChoice(selectedLength));
            formData.append('nonce', window.PodcastmakerScriptData.nonce);
            if (selectedModel) formData.append('model_id', selectedModel);

            try {
                const response = await fetch(window.PodcastmakerScriptData.ajaxUrl, { method: 'POST', body: formData });
                const data = await response.json();
                this.setState({ isGeneratingScript: false });
                if (data.success) {
                    const { createBlock } = wp.blocks;
                    const scriptParagraphs = buildScriptParagraphs(data.data.script, this.state.scriptMode);
                    const blocks = scriptParagraphs.map((paragraph) => createBlock('core/paragraph', { content: paragraph }));
                    this.props.insertBlocks(blocks);
                    this.setState({ hasAppliedSmartTone: false });
                    this.props.editPost({
                        meta: {
                            ...this.props.meta,
                            _Podcastmaker_smart_tone_applied: '0',
                        }
                    });
                    this.maybeApplyVoiceDirectionTemplateAfterScriptGeneration();
                    this.finishGenerationProgress('Script generated.');
                } else {
                    this.failGenerationProgress(data.data && data.data.message ? data.data.message : __('Error generating script', 'quikvox-ai'));
                    alert(__('Error generating script: ', 'quikvox-ai') + data.data.message);
                }
            } catch (err) {
                this.setState({ isGeneratingScript: false });
                this.failGenerationProgress(err.message || __('Failed to generate script', 'quikvox-ai'));
                alert(__('Failed to generate script: ', 'quikvox-ai') + (err.message || __('Unknown error', 'quikvox-ai')));
            }
        }

        async handleGenerateVoice() {
            const {
                scriptMode,
                selectedVoice,
                selectedTtsModel,
                multiSpeakerMap,
                detectedSpeakers,
                validationState,
            } = this.state;
            const plainText = getScriptPlainTextFromBlocks(this.props.blocks);

            if (!plainText) {
                return alert(__('Please create or enter a script before generating voice.', 'quikvox-ai'));
            }

            if (scriptMode === 'multi') {
                if (validationState.criticalErrors.length > 0) {
                    return alert(validationState.criticalErrors[0]);
                }
            } else if (!selectedVoice) {
                return alert(__('Select a voice before generating audio.', 'quikvox-ai'));
            }

            if (scriptMode === 'multi') {
                try {
                    await this.handleMultiSpeakerVoiceGeneration(plainText);
                } catch (err) {
                    this.setState({
                        isGeneratingVoice: false,
                    });
                    this.failGenerationProgress(err.message || __('Failed to generate voice', 'quikvox-ai'));
                    alert(__('Error generating voice: ', 'quikvox-ai') + (err.message || __('Unknown error', 'quikvox-ai')));
                }
                return;
            }

            this.startGenerationProgress('generate-voice', {
                textLength: plainText.length,
                chunkCount: 0,
            });
            this.setState({ isGeneratingVoice: true, isEmbedded: false });

            const formData = new FormData();
            formData.append('action', 'Podcastmaker_generate_voice');
            formData.append('text', plainText);
            formData.append('script_mode', scriptMode);
            formData.append('voice_id', scriptMode === 'single' ? selectedVoice : '');
            formData.append('speaker_map_json', JSON.stringify(multiSpeakerMap || {}));
            formData.append('tts_model_id', selectedTtsModel);
            formData.append('post_id', this.props.postId);
            formData.append('voice_direction', (this.props.meta && this.props.meta._Podcastmaker_voice_direction) ? this.props.meta._Podcastmaker_voice_direction : '');
            formData.append('nonce', window.PodcastmakerScriptData.nonce);

            try {
                const res = await fetch(window.PodcastmakerScriptData.ajaxUrl, { method: 'POST', body: formData });
                const data = await res.json();
                if (data.success) {
                    const returnedSpeakerMap = parseSpeakerMapJson(data.data.speaker_map_json);
                    const generatedSummaryLabel = data.data.voice_summary_label || buildVoiceSummaryLabel(
                        scriptMode,
                        selectedVoice,
                        returnedSpeakerMap,
                        detectedSpeakers
                    );

                    this.setState({
                        generatedAudioUrl: data.data.audio_url,
                        generatedAttachId: data.data.attach_id || 0,
                        generatedVoiceSummaryLabel: generatedSummaryLabel,
                        multiSpeakerMap: scriptMode === 'multi' ? returnedSpeakerMap : this.state.multiSpeakerMap,
                        isGeneratingVoice: false,
                        isEmbedded: false
                    });
                    this.finishGenerationProgress(__('Voice generated.', 'quikvox-ai'));
                    this.props.editPost({
                        meta: {
                            ...this.props.meta,
                            _Podcastmaker_audio_url: data.data.audio_url,
                            _Podcastmaker_audio_id: data.data.attach_id || 0,
                            _Podcastmaker_audio_embedded: '0',
                            _Podcastmaker_script_mode: scriptMode,
                            _Podcastmaker_voice_id: scriptMode === 'single' ? selectedVoice : '',
                            _Podcastmaker_voice_summary_label: generatedSummaryLabel,
                            _Podcastmaker_multi_speaker_map: JSON.stringify(
                                scriptMode === 'multi' ? returnedSpeakerMap : (multiSpeakerMap || {})
                            ),
                        }
                    });
                } else {
                    this.setState({ isGeneratingVoice: false });
                    this.failGenerationProgress(data.data && data.data.message ? data.data.message : __('Error generating voice', 'quikvox-ai'));
                    alert(__('Error generating voice: ', 'quikvox-ai') + data.data.message);
                }
            } catch (err) {
                this.setState({ isGeneratingVoice: false });
                this.failGenerationProgress(err.message || __('Failed to generate voice', 'quikvox-ai'));
                alert(__('Failed to generate voice: ', 'quikvox-ai') + (err.message || __('Unknown error', 'quikvox-ai')));
            }
        }

        async handleApplySmartTone() {
            const { selectedToneStyle, isPro } = this.state;
            if (!isPro && !this.state.isBusiness) {
                return alert(__('This feature is available for Pro and Business plan users.', 'quikvox-ai'));
            }

            const textContent = getScriptPlainTextFromBlocks(this.props.blocks);

            if (!textContent.trim()) {
                return alert(__('Script content is empty. Generate or write a script first.', 'quikvox-ai'));
            }

            this.startGenerationProgress('apply-smart-tone', {
                textLength: textContent.length,
            });
            this.setState({ isApplyingSmartTone: true });

            const formData = new FormData();
            formData.append('action', 'Podcastmaker_apply_smart_tone');
            formData.append('text', textContent);
            formData.append('style', selectedToneStyle);
            formData.append('post_id', this.props.postId || 0);
            formData.append('voice_direction', (this.props.meta && this.props.meta._Podcastmaker_voice_direction) ? this.props.meta._Podcastmaker_voice_direction : '');
            formData.append('nonce', window.PodcastmakerScriptData.nonce);

            try {
                const res = await fetch(window.PodcastmakerScriptData.ajaxUrl, { method: 'POST', body: formData });
                const data = await res.json();
                if (data.success) {
                    const newText = data.data.text;
                    const { createBlock } = wp.blocks;
                    const smartToneParagraphs = buildScriptParagraphs(newText, this.state.scriptMode);
                    const newBlocks = smartToneParagraphs.map((paragraph) => createBlock('core/paragraph', { content: paragraph }));

                    // Replace all blocks in the editor
                    const { resetBlocks } = wp.data.dispatch('core/block-editor');
                    resetBlocks(newBlocks);

                this.setState({
                    isApplyingSmartTone: false,
                    hasAppliedSmartTone: true,
                });
                this.props.editPost({
                    meta: {
                        ...this.props.meta,
                        _Podcastmaker_smart_tone_applied: '1',
                    }
                });
                this.finishGenerationProgress(__('Smart Tone applied.', 'quikvox-ai'));
                } else {
                    this.setState({ isApplyingSmartTone: false });
                    this.failGenerationProgress(data.data && data.data.message ? data.data.message : __('Error applying smart tone', 'quikvox-ai'));
                    alert(__('Error applying smart tone: ', 'quikvox-ai') + data.data.message);
                }
            } catch (err) {
                this.setState({ isApplyingSmartTone: false });
                this.failGenerationProgress(err.message || __('Failed to apply smart tone', 'quikvox-ai'));
                alert(__('Failed to apply smart tone: ', 'quikvox-ai') + (err.message || __('Unknown error', 'quikvox-ai')));
            }
        }

        handleInsertToSource() {
            const {
                sourcePostId,
                generatedAudioUrl,
                generatedAttachId,
                selectedVoice,
                scriptMode,
                generatedVoiceSummaryLabel,
                multiSpeakerMap,
                detectedSpeakers,
            } = this.state;
            if (!sourcePostId || !generatedAudioUrl) return;

            this.setState({ isInserting: true });

            const voiceSummaryLabel = generatedVoiceSummaryLabel || buildVoiceSummaryLabel(
                scriptMode,
                selectedVoice,
                multiSpeakerMap,
                detectedSpeakers
            );

            const formData = new FormData();
            formData.append('action', 'Podcastmaker_insert_audio_to_post');
            formData.append('target_post_id', sourcePostId);
            formData.append('audio_url', generatedAudioUrl);
            formData.append('attach_id', generatedAttachId || 0);
            formData.append('voice_id', scriptMode === 'single' ? (selectedVoice || '') : '');
            formData.append('audio_mode', scriptMode);
            formData.append('voice_summary_label', voiceSummaryLabel);
            formData.append('speaker_map_json', JSON.stringify(multiSpeakerMap || {}));
            formData.append('script_post_id', this.props.postId || 0);
            formData.append('nonce', window.PodcastmakerScriptData.nonce);

            fetch(window.PodcastmakerScriptData.ajaxUrl, { method: 'POST', body: formData })
                .then(res => res.json())
                .then(data => {
                    if (data.success) {
                        wp.data.dispatch('core/editor').savePost();
                        this.setState({ isEmbedded: true });
                        this.props.editPost({
                            meta: {
                                ...this.props.meta,
                                _Podcastmaker_audio_embedded: '1',
                            }
                        });

                        // Redirect to source article with success message
                        const { sourcePostId } = this.state;
                        if (sourcePostId) {
                            alert(__('Audio has been embedded into the article. Returning to the original editor.', 'quikvox-ai'));
                            const adminUrl = window.PodcastmakerScriptData?.adminUrl || window.location.origin + '/wp-admin/';
                            const sourceUrl = `${adminUrl}post.php?post=${sourcePostId}&action=edit`;
                            window.location.href = sourceUrl;
                        } else {
                            alert(__('Success! Audio embedded and script published.', 'quikvox-ai'));
                        }
                    } else {
                        alert(__('Error inserting audio: ', 'quikvox-ai') + data.data.message);
                    }
                    this.setState({ isInserting: false });
                })
                .catch((err) => {
                    this.setState({ isInserting: false });
                    alert(__('Failed to insert audio: ', 'quikvox-ai') + (err.message || __('Unknown error', 'quikvox-ai')));
                });
        }

        render() {
            const {
                sourceContent, sourceTitle, sourcePostId, sourcePosts,
                selectedModel, geminiModels, selectedPrompt, promptSets, promptSetLookup, isGeneratingScript,
                selectedVoice, voices, isGeneratingVoice, generatedAudioUrl, selectedTtsModel, ttsModels,
                isMobile, lastError, scriptMode, isPro, isBusiness, validationState, multiSpeakerMap,
                detectedSpeakers, generatedVoiceSummaryLabel, selectedLanguage, selectedLength, isContextLayerOpen, contextDraft,
                isVoiceLayerOpen, isSmartToneLayerOpen
            } = this.state;

            const splitViewTarget = document.querySelector('.interface-interface-skeleton__content');
            const canUseVoiceDirection = isPro || isBusiness;
            const voiceDirectionMount = canUseVoiceDirection ? ensureVoiceDirectionMount() : null;
            const plainText = getScriptPlainTextFromBlocks(this.props.blocks);
            const activeScriptMode = normalizeScriptMode(scriptMode, isPro || isBusiness);
            const activeLanguageCode = normalizeLanguageCode(selectedLanguage || '');
            const activeLengthChoice = normalizeLengthChoice(selectedLength);
            const activeContextSelection = contextDraft || this.getActiveContextDraft();
            const activeGeminiModel = resolveAvailableModel(
                activeContextSelection.modelId || selectedModel || '',
                geminiModels,
                selectedModel || ''
            );
            const activeDraftLanguage = normalizeLanguageCode(activeContextSelection.language);
            const activeDraftLength = normalizeLengthChoice(activeContextSelection.length);
            const voiceDirectionValue = canUseVoiceDirection && this.props.meta && this.props.meta._Podcastmaker_voice_direction
                ? String(this.props.meta._Podcastmaker_voice_direction)
                : '';
            const selectedPromptData = selectedPrompt ? promptSetLookup[selectedPrompt] : null;
            const selectedPromptSpeakerType = selectedPromptData && selectedPromptData.speaker_type === 'multi' ? 'multi' : 'single';
            const selectedPromptLanguage = selectedPromptData ? normalizeLanguageCode(selectedPromptData.language || '') : '';
            const selectedPromptCompatible = !selectedPrompt || (
                selectedPromptSpeakerType === activeScriptMode
                && (!activeLanguageCode || selectedPromptLanguage === activeLanguageCode)
            );
            const filteredToneStyles = getFilteredSmartToneStyles(this.state.smartToneStyles, activeScriptMode);
            const activeToneStyle = filteredToneStyles.some((option) => option.value === this.state.selectedToneStyle)
                ? this.state.selectedToneStyle
                : (filteredToneStyles[0] ? filteredToneStyles[0].value : '');
            const activeToneStyleDefinition = filteredToneStyles.find((option) => option.value === activeToneStyle) || null;
            const ttsModelOptions = buildTtsModelOptions(ttsModels, selectedTtsModel);
            const canUseMultiSpeaker = isPro || isBusiness;
            const showSmartToneStep = isPro || isBusiness;
            const createVoiceStepIndex = showSmartToneStep ? 'Step 4' : 'Step 3';
            const embedArticleStepIndex = showSmartToneStep ? 'Step 5' : 'Step 4';
            const pendingSmartToneStyleChange = this.state.pendingSmartToneStyleChange;
            const pendingSmartToneStyleDefinition = pendingSmartToneStyleChange
                ? (this.state.smartToneStyles || []).find((option) => option.value === pendingSmartToneStyleChange.nextToneStyle) || null
                : null;
            const dialogueTurnsPreview = buildDialogueTurnsPreview(this.props.blocks, activeScriptMode);
            const chunkPlanPreview = activeScriptMode === 'multi'
                ? buildMultiSpeakerChunkPlanForPreview(
                    dialogueTurnsPreview,
                    voiceDirectionValue,
                    (window.PodcastmakerScriptData && window.PodcastmakerScriptData.multiSpeakerChunkConfig && window.PodcastmakerScriptData.multiSpeakerChunkConfig.maxTotalBytes)
                        ? Number(window.PodcastmakerScriptData.multiSpeakerChunkConfig.maxTotalBytes)
                        : 3200
                )
                : [];
            const ttsRequestPreview = activeScriptMode === 'multi'
                ? chunkPlanPreview.map((chunk) => ({
                    chunkIndex: chunk.chunkIndex,
                    turnCount: chunk.turnCount,
                    turnRange: chunk.turnRange,
                    payloadBytes: chunk.payloadBytes,
                    payload: buildMultiSpeakerTtsPayloadPreview(chunk.turns, multiSpeakerMap, voiceDirectionValue),
                }))
                : [{
                    chunkIndex: 0,
                    turnCount: 1,
                    turnRange: [],
                    payloadBytes: countUtf8Bytes(buildSingleSpeakerPromptPreview(plainText, voiceDirectionValue)),
                    payload: buildSingleSpeakerTtsPayloadPreview(plainText, selectedVoice, voiceDirectionValue),
                }];
            const payloadPreview = {
                scriptMode: activeScriptMode,
                smartToneStyle: activeToneStyle,
                voiceDirection: voiceDirectionValue,
                ttsRequests: ttsRequestPreview,
                ...(activeScriptMode === 'multi' ? {
                    chunkPlan: chunkPlanPreview.map((chunk) => ({
                        chunkIndex: chunk.chunkIndex,
                        turnCount: chunk.turnCount,
                        turnRange: chunk.turnRange,
                        payloadBytes: chunk.payloadBytes,
                    })),
                    detectedSpeakers,
                    speakerMap: multiSpeakerMap,
                } : {}),
                validation: validationState,
            };
            const languageOptions = buildLanguageOptionsFromPromptSets(promptSets, promptSetLookup);
            const draftPromptSets = buildPromptSetOptionsForContext(promptSets, promptSetLookup, activeDraftLanguage, activeScriptMode);
            const draftPromptOptions = draftPromptSets.map((promptOption) => ({
                label: promptOption.label,
                value: promptOption.value,
            }));
            const summaryLanguageLabel = getLanguageDisplayLabel(activeLanguageCode || selectedPromptLanguage || 'en');
            const summaryPromptLabel = selectedPromptData && selectedPromptData.name ? selectedPromptData.name : __('Prompt Set', 'quikvox-ai');
            const summaryItems = [
                { label: __('Language', 'quikvox-ai'), value: summaryLanguageLabel },
                { label: __('Length', 'quikvox-ai'), value: activeLengthChoice },
                { label: __('Gemini Model', 'quikvox-ai'), value: selectedModel || __('Default', 'quikvox-ai') },
                { label: __('Prompt Set', 'quikvox-ai'), value: summaryPromptLabel },
            ];

            const summaryLabel = generatedVoiceSummaryLabel || buildVoiceSummaryLabel(
                activeScriptMode,
                selectedVoice,
                multiSpeakerMap,
                detectedSpeakers
            );
            const hasScriptContent = plainText.trim().length > 0;
            const isScriptGenerationComplete = hasScriptContent && !isGeneratingScript;
            const canGenerateVoice = activeScriptMode === 'multi'
                ? validationState.criticalErrors.length === 0 && !!plainText
                : !!selectedVoice && !!plainText;
            const canGenerateScript = !!sourcePostId && !!selectedPrompt;
            const progressMessage = this.state.generationProgress && this.state.generationProgress.stage
                ? this.state.generationProgress.stage
                : '';

            // Debug Log
            if (window.innerWidth <= 782 && !isMobile) {
                console.warn('Podcastmaker: isMobile state mismatch!', isMobile, window.innerWidth);
            }

            const smartToneConfirmDialog = pendingSmartToneStyleChange && document.body ? wp.element.createPortal(
                createElement('div', {
                    className: 'pm-smart-tone-confirm-overlay',
                    role: 'presentation',
                    onClick: this.cancelSmartToneStyleChange
                },
                    createElement('div', {
                        className: 'pm-smart-tone-confirm-modal',
                        role: 'dialog',
                        'aria-modal': 'true',
                        'aria-labelledby': 'pm-smart-tone-confirm-title',
                        onClick: (event) => event.stopPropagation()
                    },
                        createElement('div', { className: 'pm-smart-tone-confirm-modal__header' },
                            createElement('strong', {
                                id: 'pm-smart-tone-confirm-title',
                                className: 'pm-smart-tone-confirm-modal__title'
                            }, __('Replace Voice Direction?', 'quikvox-ai')),
                            pendingSmartToneStyleDefinition && createElement('span', { className: 'pm-smart-tone-confirm-modal__badge' }, pendingSmartToneStyleDefinition.label)
                        ),
                        createElement('p', { className: 'pm-smart-tone-confirm-modal__message' },
                            __('The current Voice Direction has been customized. Switching SMART TONE styles will replace it with the template for the new style.', 'quikvox-ai')
                        ),
                        createElement('p', { className: 'pm-smart-tone-confirm-modal__hint' },
                            __('Choose Replace to apply the new template, or Cancel to keep the current Voice Direction.', 'quikvox-ai')
                        ),
                        createElement('div', { className: 'pm-smart-tone-confirm-modal__actions' },
                            createElement('button', {
                                type: 'button',
                                className: 'button button-secondary',
                                onClick: this.cancelSmartToneStyleChange
                            }, __('Cancel', 'quikvox-ai')),
                            createElement('button', {
                                type: 'button',
                                className: 'button button-primary',
                                onClick: this.confirmSmartToneStyleChange
                            }, __('Replace', 'quikvox-ai'))
                        )
                    )
                ),
                document.body
            ) : null;

            const contextSelectionModal = isContextLayerOpen ? createElement(Modal, {
                className: 'pm-context-modal',
                title: __('Script Modal', 'quikvox-ai'),
                onRequestClose: this.handleContextLayerClose,
                shouldCloseOnClickOutside: true,
                shouldCloseOnEsc: true,
            },
                createElement('div', { className: 'pm-context-modal__body' },
                    createElement('div', { className: 'pm-context-modal__hero' },
                        createElement('div', null,
                            createElement('strong', { className: 'pm-context-modal__hero-title' }, __('Script Modal', 'quikvox-ai')),
                            createElement('p', { className: 'pm-context-modal__hero-text' }, __('Language, length, model, and prompt set are decided here. Select saves and returns, Create Script generates immediately.', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-context-modal__hero-badge' }, sprintf(__('Step 2 · %s', 'quikvox-ai'), getLanguageDisplayLabel(activeDraftLanguage || selectedPromptLanguage || 'en')))
                    ),
                    createElement('div', { className: 'pm-context-modal__panel' },
                        createElement('div', { className: 'pm-context-modal__panel-head' },
                            createElement('strong', null, __('Script settings', 'quikvox-ai')),
                            createElement('span', null, __('inherit source article context', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-context-modal__panel-body' },
                            createElement('div', { className: 'pm-context-modal__section' },
                                createElement(SelectControl, {
                                    label: __('Gemini Model', 'quikvox-ai'),
                                    value: activeGeminiModel,
                                    options: geminiModels.map((modelName) => ({
                                        label: modelName,
                                        value: modelName,
                                    })),
                                    onChange: (val) => this.handleContextDraftChange('modelId', val),
                                })
                            ),
                            createElement('div', { className: 'pm-context-modal__section' },
                                createElement(SelectControl, {
                                    label: __('Language', 'quikvox-ai'),
                                    value: activeDraftLanguage,
                                    options: languageOptions,
                                    onChange: (val) => this.handleContextDraftChange('language', val),
                                })
                            ),
                            createElement('div', { className: 'pm-context-modal__section' },
                                createElement('div', { className: 'pm-context-modal__section-label' }, __('Length', 'quikvox-ai')),
                                createElement('div', { className: 'pm-context-modal__button-group', role: 'group', 'aria-label': __('Length', 'quikvox-ai') },
                                    LENGTH_OPTIONS.map((lengthOption) => createElement('button', {
                                        key: lengthOption.value,
                                        type: 'button',
                                        className: `pm-context-modal__button ${activeDraftLength === lengthOption.value ? 'is-active' : ''}`,
                                        'aria-pressed': activeDraftLength === lengthOption.value,
                                        onClick: () => this.handleContextDraftChange('length', lengthOption.value),
                                    }, lengthOption.label))
                                )
                            ),
                            createElement('div', { className: 'pm-context-modal__section' },
                                createElement(SelectControl, {
                                    label: __('Prompt Set', 'quikvox-ai'),
                                    value: activeContextSelection.promptId || '',
                                    options: draftPromptOptions.length > 0 ? draftPromptOptions : [{ label: __('No prompt sets available', 'quikvox-ai'), value: '' }],
                                    onChange: (val) => this.handleContextDraftChange('promptId', val),
                                    disabled: draftPromptOptions.length === 0,
                                })
                            ),
                            draftPromptOptions.length === 0 && createElement('div', { className: 'pm-validation-message is-warning' },
                                __('No prompt set matches the current language and mode.', 'quikvox-ai')
                            )
                        )
                    ),
                    createElement('div', { className: 'pm-context-modal__panel' },
                        createElement('div', { className: 'pm-context-modal__panel-head' },
                            createElement('strong', null, __('Context summary', 'quikvox-ai')),
                            createElement('span', null, __('what the modal keeps', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-context-modal__panel-body' },
                            summaryItems.map((item) => createElement('div', {
                                key: item.label,
                                className: 'pm-context-summary-card__row'
                            },
                                createElement('span', { className: 'pm-context-summary-card__row-label' }, `${item.label}:`),
                                createElement('span', { className: 'pm-context-summary-card__row-value' }, item.value)
                            )),
                            createElement('div', { className: 'pm-generation-progress-note', style: { marginTop: '10px' } },
                                __('This is the same script context that will be used when Create Script runs.', 'quikvox-ai')
                            )
                        )
                    ),
                    createElement('div', { className: 'pm-context-modal__footer' },
                        createElement('div', { className: 'pm-context-modal__footer-actions' },
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleContextLayerClose,
                            }, __('Close', 'quikvox-ai')),
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleContextLayerSelect,
                            }, __('Select', 'quikvox-ai')),
                            createElement(Button, {
                                isPrimary: true,
                                onClick: this.handleContextLayerCreate,
                                disabled: draftPromptOptions.length === 0,
                            }, __('Create Script', 'quikvox-ai'))
                        )
                    )
                )
            ) : null;

            const voiceSelectionModal = isVoiceLayerOpen ? createElement(Modal, {
                className: 'pm-context-modal pm-voice-modal',
                title: __('Voice Modal', 'quikvox-ai'),
                onRequestClose: this.handleVoiceLayerClose,
                shouldCloseOnClickOutside: true,
                shouldCloseOnEsc: true,
            },
                createElement('div', { className: 'pm-context-modal__body' },
                    activeScriptMode === 'single'
                        ? (() => {
                            const singleSelection = this.getSingleSpeakerSelection();
                            const singleVoiceOptions = singleSelection.voiceOptions || [];
                            const singleVoiceId = singleSelection.selectedVoiceId || '';
                            const singleVoice = singleSelection.selectedVoice;

                            return createElement('div', { className: 'pm-single-speaker-workspace' },
                                createElement('div', { className: 'pm-speaker-map-list' },
                                    createElement('div', { className: 'pm-speaker-map-card pm-speaker-map-card--speaker-0 pm-speaker-map-card--single' },
                                        createElement('div', { className: 'pm-speaker-map-card__header' },
                                            createElement('span', { className: 'pm-speaker-chip' }, __('Speaker 1', 'quikvox-ai')),
                                            createElement('strong', { className: 'pm-speaker-map-card__name' }, __('Primary', 'quikvox-ai'))
                                        ),
                                        createElement('div', { className: 'pm-speaker-map-card__body' },
                                            createElement('div', { className: 'pm-speaker-map-card__toolbar' },
                                                createElement('div', {
                                                    className: 'pm-voice-gender-toggle',
                                                    role: 'group',
                                                    'aria-label': __('Voice gender toggle', 'quikvox-ai')
                                                },
                                                    createElement('button', {
                                                        type: 'button',
                                                        className: `pm-voice-toggle ${singleSelection.selectedGender === 'Male' ? 'is-active' : ''}`,
                                                        onClick: () => this.handleVoiceGenderChange('Male'),
                                                        'aria-pressed': singleSelection.selectedGender === 'Male'
                                                    }, createElement('span', { className: 'pm-voice-toggle__icon' }, '♂'), 'Male'),
                                                    createElement('button', {
                                                        type: 'button',
                                                        className: `pm-voice-toggle ${singleSelection.selectedGender === 'Female' ? 'is-active' : ''}`,
                                                        onClick: () => this.handleVoiceGenderChange('Female'),
                                                        'aria-pressed': singleSelection.selectedGender === 'Female'
                                                    }, createElement('span', { className: 'pm-voice-toggle__icon' }, '♀'), 'Female')
                                                )
                                            ),
                                            createElement('div', { className: 'pm-speaker-map-card__select-shell' },
                                                createElement('select', {
                                                    className: 'pm-speaker-map-card__select',
                                                    value: singleVoiceId,
                                                    onChange: (event) => {
                                                        const selectedOptionVoice = findVoiceById(singleVoiceOptions, event.target.value);
                                                        if (selectedOptionVoice) {
                                                            this.handleVoiceSelection(selectedOptionVoice);
                                                        }
                                                    },
                                                        'aria-label': __('Voice selection', 'quikvox-ai')
                                                },
                                                    singleVoiceOptions.length > 0
                                                        ? singleVoiceOptions.map((voice) => {
                                                            const optionLabel = voice.characteristics
                                                                ? `${voice.voice_id} · ${voice.characteristics}`
                                                                : voice.voice_id;

                                                            return createElement('option', {
                                                                key: voice.voice_id,
                                                                value: voice.voice_id
                                                            }, optionLabel);
                                                        })
                                                        : createElement('option', { value: '', disabled: true }, __('No voices available', 'quikvox-ai'))
                                                ),
                                                createElement('div', { className: 'pm-speaker-map-card__select-helper' }, __('Select is saved immediately.', 'quikvox-ai'))
                                            )
                                        ),
                                        createElement('div', { className: 'pm-speaker-map-card__footer' },
                                            createElement('div', { className: 'pm-speaker-map-card__selection-chip' },
                                                createElement('span', { className: 'pm-speaker-map-card__selection-chip-dot' }),
                                                createElement('span', null,
                                                    singleVoice
                                                        ? sprintf(__('Selected: %s', 'quikvox-ai'), singleVoice.voice_id)
                                                        : __('Selected: none', 'quikvox-ai')
                                                )
                                            ),
                                            createElement('button', {
                                                type: 'button',
                                                className: `pm-play-btn ${this.state.playingVoice === singleVoiceId ? 'is-playing' : ''}`,
                                                disabled: !singleVoiceId,
                                                onClick: (event) => this.togglePreview(singleVoiceId, event)
                                            }, this.state.playingVoice === singleVoiceId ? '■' : '▶')
                                        )
                                    )
                                ),
                                createElement('div', { className: 'pm-context-modal__panel pm-voice-modal__tts-panel' },
                                    createElement('div', { className: 'pm-context-modal__panel-head' },
                                        createElement('strong', null, __('TTS model', 'quikvox-ai')),
                                        createElement('span', null, __('used when Create Voice runs', 'quikvox-ai'))
                                    ),
                                    createElement('div', { className: 'pm-context-modal__panel-body' },
                                        createElement(SelectControl, {
                                            label: __('TTS Model', 'quikvox-ai'),
                                            value: this.state.selectedTtsModel,
                                            options: ttsModelOptions,
                                            onChange: this.handleTtsModelChange,
                                        })
                                    )
                                )
                            );
                        })()
                        : createElement('div', { className: 'pm-multi-speaker-workspace' },
                            detectedSpeakers.length === 0
                                ? createElement('div', { className: 'pm-empty-state-card' }, __('No speakers detected yet.', 'quikvox-ai'))
                                : createElement('div', { className: 'pm-speaker-map-list' },
                                    detectedSpeakers.map((speakerName, index) => {
                                        const speakerSelection = this.getMultiSpeakerSelection(speakerName);
                                        const mappedVoiceId = speakerSelection.selectedVoiceId || '';
                                        const mappedVoice = speakerSelection.selectedVoice;
                                        const voiceOptions = speakerSelection.voiceOptions || [];
                                        return createElement('div', {
                                            key: speakerName,
                                            className: `pm-speaker-map-card pm-speaker-map-card--speaker-${Math.min(index, 2)}`
                                        },
                                            createElement('div', { className: 'pm-speaker-map-card__header' },
                                                createElement('span', { className: 'pm-speaker-chip' }, sprintf(__('Speaker %d', 'quikvox-ai'), index + 1)),
                                                createElement('strong', { className: 'pm-speaker-map-card__name' }, speakerName)
                                            ),
                                            createElement('div', { className: 'pm-speaker-map-card__body' },
                                                createElement('div', { className: 'pm-speaker-map-card__toolbar' },
                                                    createElement('div', {
                                                        className: 'pm-voice-gender-toggle',
                                                        role: 'group',
                                                        'aria-label': __('Voice gender toggle', 'quikvox-ai')
                                                    },
                                                        createElement('button', {
                                                            type: 'button',
                                                            className: `pm-voice-toggle ${speakerSelection.selectedGender === 'Male' ? 'is-active' : ''}`,
                                                            onClick: () => this.handleSpeakerVoiceGenderChange(speakerName, 'Male'),
                                                            'aria-pressed': speakerSelection.selectedGender === 'Male'
                                                        }, createElement('span', { className: 'pm-voice-toggle__icon' }, '♂'), 'Male'),
                                                        createElement('button', {
                                                            type: 'button',
                                                            className: `pm-voice-toggle ${speakerSelection.selectedGender === 'Female' ? 'is-active' : ''}`,
                                                            onClick: () => this.handleSpeakerVoiceGenderChange(speakerName, 'Female'),
                                                            'aria-pressed': speakerSelection.selectedGender === 'Female'
                                                        }, createElement('span', { className: 'pm-voice-toggle__icon' }, '♀'), 'Female')
                                                    )
                                                ),
                                                createElement('div', { className: 'pm-speaker-map-card__select-shell' },
                                                    createElement('select', {
                                                        className: 'pm-speaker-map-card__select',
                                                        value: mappedVoiceId,
                                                        onChange: (event) => this.handleSpeakerVoiceChange(speakerName, event.target.value),
                                                        'aria-label': sprintf(__('Voice selection for %s', 'quikvox-ai'), speakerName)
                                                    },
                                                        voiceOptions.length > 0
                                                            ? voiceOptions.map((voice) => {
                                                                const optionLabel = voice.characteristics
                                                                    ? `${voice.voice_id} · ${voice.characteristics}`
                                                                    : voice.voice_id;

                                                                return createElement('option', {
                                                                    key: voice.voice_id,
                                                                    value: voice.voice_id
                                                                }, optionLabel);
                                                            })
                                                            : createElement('option', { value: '', disabled: true }, __('No voices available', 'quikvox-ai'))
                                                    ),
                                                    createElement('div', { className: 'pm-speaker-map-card__select-helper' }, __('Select is saved immediately.', 'quikvox-ai'))
                                                ),
                                            ),
                                            createElement('div', { className: 'pm-speaker-map-card__footer' },
                                                createElement('div', { className: 'pm-speaker-map-card__selection-chip' },
                                                    createElement('span', { className: 'pm-speaker-map-card__selection-chip-dot' }),
                                                    createElement('span', null,
                                                        mappedVoice
                                                            ? sprintf(__('Selected: %s', 'quikvox-ai'), mappedVoice.voice_id)
                                                            : __('Selected: none', 'quikvox-ai')
                                                    )
                                                ),
                                                createElement('button', {
                                                    type: 'button',
                                                    className: `pm-play-btn ${this.state.playingVoice === mappedVoiceId ? 'is-playing' : ''}`,
                                                    disabled: !mappedVoiceId,
                                                    onClick: (event) => this.togglePreview(mappedVoiceId, event)
                                                }, this.state.playingVoice === mappedVoiceId ? '■' : '▶')
                                            )
                                        );
                                    })
                                )
                    ),
                    activeScriptMode === 'multi' ? createElement('div', { className: 'pm-context-modal__panel pm-voice-modal__tts-panel' },
                        createElement('div', { className: 'pm-context-modal__panel-head' },
                            createElement('strong', null, __('TTS model', 'quikvox-ai')),
                            createElement('span', null, __('used when Create Voice runs', 'quikvox-ai'))
                        ),
                        createElement('div', { className: 'pm-context-modal__panel-body' },
                            createElement(SelectControl, {
                                label: __('TTS Model', 'quikvox-ai'),
                                value: this.state.selectedTtsModel,
                                options: ttsModelOptions,
                                onChange: this.handleTtsModelChange,
                            })
                        )
                    ) : null,
                    createElement('div', { className: 'pm-context-modal__footer' },
                        createElement('div', { className: 'pm-context-modal__footer-actions' },
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleVoiceLayerClose,
                            }, __('Close', 'quikvox-ai')),
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleVoiceLayerSelect,
                            }, __('Select', 'quikvox-ai')),
                            createElement(Button, {
                                isPrimary: true,
                                onClick: this.handleVoiceLayerCreate,
                                disabled: isGeneratingVoice,
                            }, __('Create Voice', 'quikvox-ai'))
                        )
                    )
                )
            ) : null;

            const smartToneSelectionModal = isSmartToneLayerOpen ? createElement(Modal, {
                className: 'pm-context-modal pm-smart-tone-modal',
                title: __('Smart Tone Modal', 'quikvox-ai'),
                onRequestClose: this.handleSmartToneLayerClose,
                shouldCloseOnClickOutside: true,
                shouldCloseOnEsc: true,
            },
                createElement('div', { className: 'pm-context-modal__body' },
                    createElement('div', { className: 'pm-context-modal__section' },
                        createElement(SelectControl, {
                            label: __('Direction Style', 'quikvox-ai'),
                            value: activeToneStyle,
                            options: filteredToneStyles,
                            onChange: this.handleSmartToneStyleChange,
                            disabled: this.state.isApplyingSmartTone
                        })
                    ),
                    createElement('div', { className: 'pm-context-modal__footer' },
                        createElement('div', { className: 'pm-context-modal__footer-actions' },
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleSmartToneLayerClose,
                            }, __('Close', 'quikvox-ai')),
                            createElement(Button, {
                                isSecondary: true,
                                onClick: this.handleSmartToneLayerSelect,
                            }, __('Select', 'quikvox-ai')),
                            createElement(Button, {
                                isPrimary: true,
                                onClick: this.handleSmartToneLayerCreate,
                                disabled: this.state.isApplyingSmartTone || isGeneratingScript,
                            }, this.state.isApplyingSmartTone ? __('Applying Tone...', 'quikvox-ai') : __('Apply Tone', 'quikvox-ai'))
                        )
                    )
                )
            ) : null;

            return createElement(Fragment, {},
                smartToneConfirmDialog,
                contextSelectionModal,
                voiceSelectionModal,
                smartToneSelectionModal,
                canUseVoiceDirection && voiceDirectionMount && wp.element.createPortal(
                    createElement('section', { className: 'podcastmaker-voice-direction-card' },
                        createElement('button', {
                            type: 'button',
                            className: 'podcastmaker-voice-direction-card__toggle',
                            onClick: this.handleToggleVoiceDirection,
                            'aria-expanded': this.state.isVoiceDirectionOpen
                        },
                            createElement('div', { className: 'podcastmaker-voice-direction-card__heading' },
                                createElement('span', { className: 'podcastmaker-voice-direction-card__eyebrow' }, __('Voice Direction', 'quikvox-ai')),
                                createElement('strong', null, __('Top-level performance context for Smart Tone and TTS', 'quikvox-ai'))
                            ),
                            createElement('span', { className: 'podcastmaker-voice-direction-card__chevron' }, this.state.isVoiceDirectionOpen ? '△' : '▽')
                        ),
                        this.state.isVoiceDirectionOpen && createElement('div', { className: 'podcastmaker-voice-direction-card__body' },
                            createElement('div', { className: 'podcastmaker-voice-direction-card__toolbar' },
                                    createElement('div', { className: 'podcastmaker-voice-direction-card__meta' },
                                    createElement('span', { className: `Podcastmaker-mode-badge is-${activeScriptMode}` }, activeScriptMode === 'multi' ? __('Multi Speaker', 'quikvox-ai') : __('Single Speaker', 'quikvox-ai')),
                                    activeToneStyleDefinition && createElement('span', { className: 'podcastmaker-voice-direction-card__style-name' }, activeToneStyleDefinition.label.replace(/\s+\(Default\)$/, ''))
                                )
                            ),
                            createElement('p', { className: 'podcastmaker-voice-direction-card__description' }, __('Edit the shared performance context used by Smart Tone and TTS.', 'quikvox-ai')),
                            createElement('label', { className: 'podcastmaker-voice-direction-card__toggle-row' },
                                createElement('input', {
                                    type: 'checkbox',
                                    checked: this.state.showPayloadPreview,
                                    onChange: this.handleTogglePayloadPreview
                                }),
                                createElement('span', null, __('Show TTS request preview', 'quikvox-ai'))
                            ),
                            this.state.showPayloadPreview && createElement('div', { className: 'podcastmaker-voice-direction-card__payload' },
                                createElement('div', {
                                    style: {
                                        display: 'flex',
                                        justifyContent: 'space-between',
                                        alignItems: 'center',
                                        gap: '12px',
                                        marginBottom: '8px',
                                        flexWrap: 'wrap'
                                    }
                                },
                                    createElement('strong', { className: 'podcastmaker-voice-direction-card__payload-title' }, __('TTS Request Preview', 'quikvox-ai')),
                                    createElement('div', {
                                        style: {
                                            display: 'flex',
                                            alignItems: 'center',
                                            gap: '8px'
                                        }
                                    },
                                        this.state.payloadCopyStatus && createElement('span', {
                                            style: {
                                                fontSize: '12px',
                                                color: '#50575e'
                                            }
                                        }, this.state.payloadCopyStatus),
                                        createElement('button', {
                                            type: 'button',
                                            className: 'button button-secondary',
                                            onClick: () => this.handleCopyPayloadPreview(payloadPreview)
                                        }, __('Copy payload', 'quikvox-ai'))
                                    )
                                ),
                                createElement('pre', { className: 'podcastmaker-voice-direction-card__payload-code' }, JSON.stringify(payloadPreview, null, 2))
                            ),
                            createElement('textarea', {
                                className: 'podcastmaker-voice-direction-card__textarea',
                                value: voiceDirectionValue,
                                placeholder: __('## Audio Profile\n...\n\n## Scene\n...\n\n## Director\'s Notes\n...\n\n## Contextual Lead-in\n...', 'quikvox-ai'),
                                onChange: (event) => this.handleVoiceDirectionChange(event.target.value)
                            })
                        )
                    ),
                    voiceDirectionMount
                ),
                // --- Source Article Preview (Left) ---
                // Conditional Rendering: DO NOT RENDER ON MOBILE
                !isMobile && sourcePostId && splitViewTarget && wp.element.createPortal(
                    createElement(SourceArticlePanel, { title: sourceTitle, content: sourceContent }),
                    splitViewTarget
                ),

                // Context selection modal is rendered below.

                // --- Podcaster Panel (Right Sidebar) ---
                createElement(PluginSidebar, {
                    name: 'podcaster-sidebar',
                    title: __('Podcaster', 'quikvox-ai'),
                    icon: 'microphone',
                },
                    createElement('div', { className: 'pm-workflow-sidebar' }, this.renderWorkflowSidebar()),
                )
            );
        }
    }

    const PodcasterSidebarWithData = compose(
        withSelect((select) => ({
            meta: select('core/editor').getEditedPostAttribute('meta'),
            content: select('core/editor').getEditedPostAttribute('content'),
            postId: select('core/editor').getCurrentPostId(),
            blocks: select('core/block-editor').getBlocks(),
        })),
        withDispatch((dispatch) => ({
            editPost: (data) => dispatch('core/editor').editPost(data),
            insertBlocks: (blocks) => dispatch('core/block-editor').insertBlocks(blocks),
        }))
    )(PodcasterSidebar);

    registerPlugin('podcastmaker-sidebar', {
        render: PodcasterSidebarWithData,
        icon: 'microphone',
    });

    // Emotion Tags feature removed (cursor-position insertion not reliably possible from outside the editor)

})(window.wp);

/**
 * Toggle Button for Source Article Panel
 * Allows users to show/hide the preview panel for better mobile experience
 */
(function () {
    'use strict';

    /**
     * Initialize the toggle button for the source panel
     */
    function initPanelToggle() {
        // Wait for the DOM to be ready
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initPanelToggle);
            return;
        }

        // Wait for the source panel to be rendered
        const checkPanel = setInterval(() => {
            const panel = document.querySelector('.Podcastmaker-source-panel');

            // Only add toggle if panel exists
            if (panel) {
                clearInterval(checkPanel);
                addToggleButton(panel);
            }
        }, 500);

        // Stop checking after 10 seconds
        setTimeout(() => clearInterval(checkPanel), 10000);
    }

    /**
     * Create and add the toggle button
     */
    function addToggleButton(panel) {
        // Check if button already exists
        if (document.querySelector('.podcastmaker-toggle-btn')) {
            return;
        }

        // Find the panel header
        const panelHeader = panel.querySelector('.panel-header');
        if (!panelHeader) {
            console.error('Podcastmaker: Panel header not found');
            return;
        }

        // Create toggle button
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'podcastmaker-toggle-btn';
        toggleBtn.setAttribute('aria-label', __('Toggle Source Article Preview', 'quikvox-ai'));
        toggleBtn.setAttribute('type', 'button');
        toggleBtn.innerHTML = __('Hide ◀', 'quikvox-ai');
        toggleBtn.title = __('Toggle preview panel visibility', 'quikvox-ai');

        // Determine initial state based on screen size and saved preference
        const savedState = localStorage.getItem('podcastmaker_panel_hidden');
        const isDesktop = window.innerWidth >= 1280;

        // Mobile (under 1280px): ALWAYS start hidden effectively
        // Desktop: Respect saved preference or default to visible

        let shouldHide;
        if (!isDesktop) {
            // Force hide on mobile/tablet to prevent layout breakage
            shouldHide = true;
        } else if (savedState !== null) {
            // Respect user's saved preference on desktop
            shouldHide = savedState === 'true';
        } else {
            // Default: show on desktop
            shouldHide = false;
        }

        if (shouldHide) {
            panel.classList.add('is-hidden');
            toggleBtn.innerHTML = __('▶ Show Source', 'quikvox-ai');
            toggleBtn.classList.add('panel-hidden');
        }

        // Click event handler
        toggleBtn.addEventListener('click', function () {
            const isHidden = panel.classList.toggle('is-hidden');

            // Update button text and class
            if (isHidden) {
                toggleBtn.innerHTML = __('▶ Show Source', 'quikvox-ai');
                toggleBtn.classList.add('panel-hidden');
                // Move button to body when panel is hidden
                document.body.appendChild(toggleBtn);
            } else {
                toggleBtn.innerHTML = __('Hide ◀', 'quikvox-ai');
                toggleBtn.classList.remove('panel-hidden');
                // Move button back to panel header
                panelHeader.appendChild(toggleBtn);
            }

            // Save state to localStorage
            localStorage.setItem('podcastmaker_panel_hidden', isHidden);

            console.log('Podcastmaker: Panel toggled', isHidden ? 'hidden' : 'visible');
        });

        // Add button to panel header (or body if panel is hidden)
        if (shouldHide) {
            document.body.appendChild(toggleBtn);
        } else {
            panelHeader.appendChild(toggleBtn);
        }

        console.log('Podcastmaker: Toggle button initialized');
    }



    // Initialize when script loads
    initPanelToggle();

})();


/**
 * Mobile Sidebar Toggle (FAB)
 * Uses WordPress Data API to toggle the sidebar
 */
(function () {
    'use strict';

    function initMobileSidebarToggle() {
        // Only run on mobile screens
        if (window.innerWidth > 782) {
            return;
        }

        // Check if FAB already exists
        if (document.querySelector('.podcastmaker-mobile-fab')) {
            return;
        }

        // Wait for WP Data API to be ready
        if (!window.wp || !window.wp.data) {
            setTimeout(initMobileSidebarToggle, 500);
            return;
        }

        // Create FAB button
        const fab = document.createElement('button');
        fab.className = 'podcastmaker-mobile-fab';
        fab.setAttribute('aria-label', __('Toggle Podcaster Panel', 'quikvox-ai'));
        fab.setAttribute('type', 'button');
        fab.innerHTML = '🎙️'; // Microphone emoji
        fab.title = __('Open Podcaster panel', 'quikvox-ai');

        // Sidebar Identifier
        // format: plugin-name/sidebar-name
        const SIDEBAR_ID = 'podcastmaker-sidebar/podcaster-sidebar';

        // Check state and update button
        const updateButtonState = () => {
            const activeSidebar = wp.data.select('core/edit-post').getActiveGeneralSidebarName();
            const isMySidebarOpen = activeSidebar === SIDEBAR_ID;

            // Update FAB
            if (isMySidebarOpen) {
                fab.innerHTML = '×';
                fab.title = __('Close Podcaster panel', 'quikvox-ai');
                fab.classList.add('is-active');
            } else {
                fab.innerHTML = '🎙️';
                fab.title = __('Open Podcaster panel', 'quikvox-ai');
                fab.classList.remove('is-active');
            }


            // Update CSS Class for Mobile Transition
            const sidebar = document.querySelector('.interface-interface-skeleton__sidebar');
            const innerSidebar = document.querySelector('.interface-complementary-area');


            if (sidebar) {
                // Stacking handled by CSS classes
                if (isMySidebarOpen) {
                    sidebar.classList.add('mobile-visible');
                    if (innerSidebar) innerSidebar.classList.add('mobile-visible');
                    // Add body class for global targeting
                    document.body.classList.add('podcastmaker-mobile-sidebar-open');


                    // Add backdrop
                    let backdrop = document.querySelector('.podcastmaker-sidebar-backdrop');
                    const skeleton = document.querySelector('.interface-interface-skeleton');
                    if (!backdrop) {
                        backdrop = document.createElement('div');
                        backdrop.className = 'podcastmaker-sidebar-backdrop active';
                        backdrop.onclick = (e) => {
                            e.stopPropagation();
                            wp.data.dispatch('core/edit-post').closeGeneralSidebar();
                        };
                        // Append to skeleton to ensure proper stacking context
                        if (skeleton) {
                            skeleton.appendChild(backdrop);
                        } else {
                            document.body.appendChild(backdrop);
                        }
                    } else {
                        if (skeleton && !skeleton.contains(backdrop)) {
                            skeleton.appendChild(backdrop);
                        }
                        backdrop.classList.add('active');
                    }

                    // No need for stopPropagation on sidebar - backdrop handles closing

                } else {
                    sidebar.classList.remove('mobile-visible');
                    if (innerSidebar) innerSidebar.classList.remove('mobile-visible');
                    // Remove body class
                    document.body.classList.remove('podcastmaker-mobile-sidebar-open');

                    const backdrop = document.querySelector('.podcastmaker-sidebar-backdrop');
                    if (backdrop) backdrop.classList.remove('active');
                }
            }
        };

        // Click handler for FAB
        fab.addEventListener('click', function () {
            const { openGeneralSidebar, closeGeneralSidebar } = wp.data.dispatch('core/edit-post');
            const activeSidebar = wp.data.select('core/edit-post').getActiveGeneralSidebarName();

            if (activeSidebar === SIDEBAR_ID) {
                // If currently open, close it
                closeGeneralSidebar();
            } else {
                // FORCE the editor into a state where sidebar is visible
                // Sometimes mobile view is stuck in 'visual' mode without sidebar container

                // 1. Ensure we are not in fullscreen mode if possible (optional, but helps on some themes)
                const isFullscreen = wp.data.select('core/edit-post').isFeatureActive('fullscreenMode');
                if (isFullscreen) {
                    // We might want to disable fullscreen, but let's try just opening sidebar first
                }

                // 2. Open our sidebar
                const SIDEBAR_ID = 'podcastmaker-sidebar/podcaster-sidebar';
                openGeneralSidebar(SIDEBAR_ID);

                // 3. Dispatch a resize event to force UI update
                setTimeout(() => {
                    window.dispatchEvent(new Event('resize'));
                }, 100);
            }

            // Update state immediately
            setTimeout(updateButtonState, 100);
        });

        // Find a place to append. Body is safest.
        document.body.appendChild(fab);

        console.log('Podcastmaker: Mobile FAB initialized via Data API');

        // Subscribe to changes to keep button in sync (e.g. if closed via "x" button)
        wp.data.subscribe(() => {
            updateButtonState();
        });

        // Initial check
        updateButtonState();
    }

    // Initialize with delay to ensure WP is ready
    if (document.readyState === 'complete') {
        setTimeout(initMobileSidebarToggle, 2000);
    } else {
        window.addEventListener('load', () => setTimeout(initMobileSidebarToggle, 2000));
    }

    // Re-initialize on window resize and enforce mobile layout
    let resizeTimer;

    // DEBUG: Layout Inspector
    function debugLayout() {
        if (window.innerWidth > 782) return; // Only debug on mobile

        const content = document.querySelector('.interface-interface-skeleton__content');
        const sidebar = document.querySelector('.interface-interface-skeleton__sidebar');
        const fab = document.querySelector('.podcastmaker-mobile-fab');
        const innerSidebar = document.querySelector('.interface-complementary-area');

        console.group('Podcastmaker Layout Debug');
        console.log('Window Width:', window.innerWidth);
        console.log('FAB Active:', fab ? fab.classList.contains('is-active') : 'No FAB');

        if (content) {
            const style = window.getComputedStyle(content);
            console.log('Content Area:', {
                display: style.display,
                visibility: style.visibility,
                opacity: style.opacity,
                width: style.width,
                marginLeft: style.marginLeft,
                left: style.left,
                position: style.position,
                zIndex: style.zIndex,
                childCount: content.childElementCount,
                rect: content.getBoundingClientRect()
            });
        } else {
            console.error('CRITICAL: Content area .interface-interface-skeleton__content NOT FOUND');
        }

        if (sidebar) {
            const style = window.getComputedStyle(sidebar);
            console.log('Sidebar (Outer):', {
                display: style.display,
                right: style.right,
                position: style.position,
                width: style.width,
                zIndex: style.zIndex,
                classList: sidebar.className,
                rect: sidebar.getBoundingClientRect()
            });
            // Border removed
        }

        if (innerSidebar) {
            const style = window.getComputedStyle(innerSidebar);
            console.log('Sidebar (Inner):', {
                display: style.display,
                right: style.right,
                width: style.width,
                height: style.height,
                zIndex: style.zIndex,
                htmlLength: innerSidebar.innerHTML.length,
                rect: innerSidebar.getBoundingClientRect()
            });
            // Border removed
        } else {
            console.log('Sidebar (Inner): NOT FOUND');
        }
        console.groupEnd();
    }

    // DEBUG: CLICK INSPECTOR (Re-enabled for verification)
    document.addEventListener('mousedown', function (e) {
        if (window.innerWidth > 782) return;
        const x = e.clientX;
        const y = e.clientY;
        const topElement = document.elementFromPoint(x, y);

        console.group('Podcastmaker Click Inspec 2.0');
        console.log('Click:', x, y);
        console.log('Target:', e.target);
        console.log('Visual Top:', topElement);

        if (topElement) {
            const style = window.getComputedStyle(topElement);
            // Check parents specifically for sidebar
            const sidebar = document.querySelector('.interface-interface-skeleton__sidebar');

            if (sidebar && (sidebar === topElement || sidebar.contains(topElement))) {
                console.log('Verdict: Clicked INSIDE Sidebar');
            } else {
                console.log('Verdict: Clicked OUTSIDE (Blocked by Top Element)');
            }
        }
        console.groupEnd();
    }, true);

    window.addEventListener('resize', function () {
        clearTimeout(resizeTimer);
        resizeTimer = setTimeout(initMobileSidebarToggle, 250);
    });


})();
