(function () {
    'use strict';

    function getRoot() {
        return document.querySelector('.aiap-wrap');
    }

    function config(name, fallback) {
        var root = getRoot();
        return root ? (root.getAttribute(name) || fallback) : fallback;
    }

    function parseMap(node) {
        if (!node) {
            return {};
        }
        try {
            return JSON.parse(node.getAttribute('data-aiap-map') || '{}');
        } catch (e) {
            return {};
        }
    }

    function renderRows(table, rows) {
        if (!table || !rows) {
            return;
        }
        var fragment = document.createDocumentFragment();
        Object.keys(rows).forEach(function (provider) {
            var tr = document.createElement('tr');
            var tdProvider = document.createElement('td');
            var tdValue = document.createElement('td');
            tdProvider.textContent = provider;
            tdValue.textContent = rows[provider];
            tr.appendChild(tdProvider);
            tr.appendChild(tdValue);
            fragment.appendChild(tr);
        });
        table.replaceChildren(fragment);
    }

    function setPreviewRatio(value) {
        var preview = document.querySelector('.aiap-format-preview');
        if (!preview) {
            return;
        }
        var dims = {
            '16:9': [180, 101],
            '1:1': [128, 128],
            '9:16': [90, 160],
            '4:3': [160, 120]
        }[value] || [180, 101];
        preview.style.width = dims[0] + 'px';
        preview.style.height = dims[1] + 'px';
        preview.textContent = value;
    }

    function setResult(result, ok, message) {
        result.classList.remove('aiap-test-result-ok', 'aiap-test-result-error', 'aiap-test-result-loading');
        result.classList.add(ok ? 'aiap-test-result-ok' : 'aiap-test-result-error');
        result.textContent = message;
        result.title = message;
    }

    function initRadioPills() {
        document.querySelectorAll('.aiap-radio-pill input[type=radio]').forEach(function (radio) {
            var pill = radio.closest('.aiap-radio-pill');
            if (radio.checked && pill) {
                pill.classList.add('checked');
            }
            radio.addEventListener('change', function () {
                var group = radio.closest('.aiap-radio-group');
                if (!group) {
                    return;
                }
                group.querySelectorAll('.aiap-radio-pill').forEach(function (item) {
                    item.classList.remove('checked');
                });
                if (radio.checked && pill) {
                    pill.classList.add('checked');
                }
            });
        });
    }

    function initCardSelects() {
        document.querySelectorAll('.js-aiap-card-select').forEach(function (select) {
            select.addEventListener('change', function () {
                document.querySelectorAll(select.getAttribute('data-card-selector')).forEach(function (card) {
                    card.hidden = true;
                });
                var card = document.getElementById(select.getAttribute('data-card-prefix') + select.value);
                if (card) {
                    card.hidden = false;
                    card.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
                var tab = document.getElementById(select.getAttribute('data-tab-target'));
                if (tab) {
                    tab.value = select.value;
                }
            });
        });
    }

    function initImageSettings() {
        var formatSelect = document.querySelector('select[name="image_format"]');
        var formatTable = document.querySelector('[data-aiap-format-table]');
        var formatMap = parseMap(formatTable);
        if (formatSelect) {
            formatSelect.addEventListener('change', function () {
                setPreviewRatio(formatSelect.value);
                renderRows(formatTable, formatMap[formatSelect.value]);
            });
            setPreviewRatio(formatSelect.value);
            renderRows(formatTable, formatMap[formatSelect.value]);
        }

        var qualitySelect = document.querySelector('select[name="image_quality"]');
        var qualityTable = document.querySelector('[data-aiap-quality-table]');
        var qualityKnob = document.querySelector('[data-aiap-quality-knob]');
        var qualityMap = parseMap(qualityTable);
        function setQuality(value) {
            renderRows(qualityTable, qualityMap[value]);
            if (qualityKnob) {
                qualityKnob.style.left = ({ low: '20%', medium: '50%', high: '80%' }[value] || '20%');
            }
        }
        if (qualitySelect) {
            qualitySelect.addEventListener('change', function () {
                setQuality(qualitySelect.value);
            });
            setQuality(qualitySelect.value);
        }
    }

    function initToggles() {
        document.addEventListener('click', function (event) {
            var toggle = event.target.closest('.js-aiap-toggle');
            if (!toggle) {
                return;
            }
            event.preventDefault();
            var target = document.getElementById(toggle.getAttribute('data-target'));
            if (target) {
                target.hidden = !target.hidden;
            }
        });
    }

    function initAddTaskToggle() {
        document.addEventListener('click', function (event) {
            var openButton = event.target.closest('.js-aiap-toggle-add-task');
            if (openButton) {
                event.preventDefault();
                // Phase 9 fix: ボタンが wrapper の外側 (セクションヘッダーなど) にあっても動くよう、
                // closest が見つからなければ document 全体から最初の wrapper を取得する
                var wrapper = openButton.closest('.aiap-add-task-wrapper') || document.querySelector('.aiap-add-task-wrapper');
                if (!wrapper) { return; }
                var form = wrapper.querySelector('.aiap-add-task-form');
                var toggleWrap = wrapper.querySelector('.aiap-add-task-toggle-wrap');
                if (!form) { return; }
                form.hidden = false;
                if (toggleWrap) { toggleWrap.hidden = true; }
                wrapper.setAttribute('data-collapsed', '0');
                // form がページ下部にある場合スクロールしてユーザーが見えるように
                try { form.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
                var firstField = form.querySelector('textarea, input:not([type=hidden])');
                if (firstField) { firstField.focus(); }
                return;
            }

            var cancelButton = event.target.closest('.js-aiap-cancel-add-task');
            if (cancelButton) {
                event.preventDefault();
                var confirmMsg = cancelButton.getAttribute('data-confirm-message') || (window.AIAP_I18N && window.AIAP_I18N.discard_changes ? window.AIAP_I18N.discard_changes : '');
                if (!window.confirm(confirmMsg)) {
                    return;
                }
                var wrapper2 = cancelButton.closest('.aiap-add-task-wrapper') || document.querySelector('.aiap-add-task-wrapper');
                if (!wrapper2) { return; }
                var form2 = wrapper2.querySelector('.aiap-add-task-form');
                var toggleWrap2 = wrapper2.querySelector('.aiap-add-task-toggle-wrap');
                if (form2) {
                    form2.reset();
                    form2.hidden = true;
                }
                if (toggleWrap2) { toggleWrap2.hidden = false; }
                wrapper2.setAttribute('data-collapsed', '1');
            }
        });
    }

    // タスク編集フォームのキャンセル: form.reset() で入力を元に戻し、<details>を閉じる
    function initCancelEditTask() {
        document.addEventListener('click', function (event) {
            var btn = event.target.closest('.js-aiap-cancel-edit-task');
            if (!btn) { return; }
            event.preventDefault();
            var form = btn.closest('form');
            if (form && typeof form.reset === 'function') {
                form.reset();
            }
            var details = btn.closest('details');
            if (details) {
                details.open = false;
            }
        });
    }

    function initRefreshModelsButtons() {
        document.addEventListener('click', function (event) {
            var button = event.target.closest('.aiap-btn-refresh-models');
            if (!button) { return; }
            event.preventDefault();
            var provider = button.getAttribute('data-provider') || '';
            if (!provider) { return; }

            var card = button.closest('.aiap-provider-card');
            var select = card ? card.querySelector('.aiap-model-select[data-provider="' + provider + '"]') : null;
            var feedback = card ? card.querySelector('.aiap-model-feedback[data-provider="' + provider + '"]') : null;
            if (!select) { return; }

            var originalHtml = button.innerHTML;
            button.disabled = true;
            button.innerHTML = '<span class="dashicons dashicons-update aiap-spin"></span> ...';
            if (feedback) { feedback.hidden = true; feedback.textContent = ''; feedback.className = 'aiap-model-feedback'; feedback.setAttribute('data-provider', provider); }

            var body = new FormData();
            body.append('action', 'aiap_refresh_models');
            body.append('nonce', config('data-aiap-nonce', ''));
            body.append('provider', provider);

            fetch(config('data-aiap-ajax-url', window.ajaxurl || ''), {
                method: 'POST',
                credentials: 'same-origin',
                body: body
            }).then(function (r) {
                return r.text().then(function (txt) {
                    var json = null;
                    try { json = JSON.parse(txt.replace(/^\uFEFF/, '').trim()); } catch (e) {}
                    return { status: r.status, json: json };
                });
            }).then(function (payload) {
                var json = payload.json;
                if (!json || !json.success) {
                    var msg = (json && json.data && json.data.message) ? json.data.message : ('HTTP ' + payload.status);
                    if (feedback) {
                        feedback.hidden = false;
                        feedback.className = 'aiap-model-feedback is-error';
                        feedback.setAttribute('data-provider', provider);
                        feedback.textContent = msg;
                    }
                    return;
                }
                var data = json.data || {};
                var models = data.models || {};
                var current = data.current || '';

                // selectのoptionを差し替え
                while (select.firstChild) { select.removeChild(select.firstChild); }
                Object.keys(models).forEach(function (id) {
                    var opt = document.createElement('option');
                    opt.value = id;
                    opt.textContent = models[id];
                    if (id === current) { opt.selected = true; }
                    select.appendChild(opt);
                });

                // 画像プロバイダの場合、スタイル欄も同期更新する
                // (サーバー側でモデル自動切替に伴うスタイル再検証が行われている)
                if (data.styles && card) {
                    applyStyleUpdate(card, provider, data.styles.options || [], data.styles.current || '');
                }

                if (feedback) {
                    feedback.hidden = false;
                    if (data.auto_switched && data.notice) {
                        feedback.className = 'aiap-model-feedback is-warning';
                        feedback.setAttribute('data-provider', provider);
                        feedback.textContent = data.notice;
                    } else {
                        feedback.className = 'aiap-model-feedback is-ok';
                        feedback.setAttribute('data-provider', provider);
                        feedback.textContent = 'OK (' + Object.keys(models).length + ')';
                    }
                }
            }).catch(function (err) {
                if (feedback) {
                    feedback.hidden = false;
                    feedback.className = 'aiap-model-feedback is-error';
                    feedback.setAttribute('data-provider', provider);
                    feedback.textContent = (err && err.message) ? err.message : (window.AIAP_I18N && window.AIAP_I18N.network_error ? window.AIAP_I18N.network_error : 'Network error');
                }
            }).then(function () {
                button.disabled = false;
                button.innerHTML = originalHtml;
            });
        });
    }

    function initEditTaskButtons() {
        document.addEventListener('click', function (event) {
            var button = event.target.closest('.js-aiap-edit-task');
            if (!button) {
                return;
            }
            event.preventDefault();
            var target = document.getElementById(button.getAttribute('data-target'));
            if (!target) {
                return;
            }
            target.open = !target.open;
            if (target.open) {
                target.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        });
    }

    function initConfirmForms() {
        document.querySelectorAll('.js-aiap-confirm').forEach(function (form) {
            form.addEventListener('submit', function (event) {
                if (!window.confirm(form.getAttribute('data-confirm') || (window.AIAP_I18N && window.AIAP_I18N.are_you_sure ? window.AIAP_I18N.are_you_sure : 'Are you sure?'))) {
                    event.preventDefault();
                }
            });
        });
    }

    function initApiControls() {
        document.addEventListener('click', function (event) {
            var testButton = event.target.closest('.aiap-btn-test');
            if (testButton) {
                event.preventDefault();
                runApiTest(testButton);
                return;
            }

            var showButton = event.target.closest('.aiap-btn-show');
            if (showButton) {
                event.preventDefault();
                var row = showButton.closest('.aiap-api-row') || showButton.parentElement;
                var input = row ? row.querySelector('input') : null;
                if (!input) {
                    return;
                }
                if (input.type === 'password') {
                    input.type = 'text';
                    showButton.textContent = config('data-aiap-btn-hide', 'Hide');
                } else {
                    input.type = 'password';
                    showButton.textContent = config('data-aiap-btn-show', 'Show');
                }
            }
        });
    }

    function runApiTest(button) {
        var card = button.closest('.aiap-provider-card');
        var row = button.closest('.aiap-api-row') || button.parentElement;
        // APIキー入力欄があればそこから、なければカード内のinputを探す (共有キー使用カード)
        var input = row ? row.querySelector('input[type=password],input[type=text]') : null;
        if (!input && card) {
            input = card.querySelector('.aiap-api-row input[type=password], .aiap-api-row input[type=text]');
        }
        var key = input ? input.value.trim() : '';
        // 既存の別要素表示があればクリアしておく (互換)
        var legacyResult = button.parentNode ? button.parentNode.querySelector('.aiap-test-result') : null;
        if (legacyResult) {
            legacyResult.textContent = '';
            legacyResult.className = 'aiap-test-result';
        }

        // 入力欄が空でもサーバーへ送信 → DB保存済みキーがあればそれを使う
        var provider = button.getAttribute('data-provider') || '';

        // ボタンを「テスト中」状態に変化
        setButtonLoading(button);

        var data = new FormData();
        data.append('action', 'aiap_test_provider');
        data.append('nonce', config('data-aiap-nonce', ''));
        data.append('kind', button.getAttribute('data-test-kind') || 'text');
        data.append('provider', button.getAttribute('data-provider') || '');
        data.append('api_key', key);
        var customModel = card && card.querySelector('input[data-aiap-custom-model]')
            ? card.querySelector('input[data-aiap-custom-model]').value.trim()
            : '';
        data.append('model', customModel || (card && card.querySelector('.aiap-model-select') ? card.querySelector('.aiap-model-select').value : (card && card.querySelector('select') ? card.querySelector('select').value : '')));
        var styleSelect = card ? card.querySelector('select[name$="_style"]') : null;
        data.append('style', styleSelect ? styleSelect.value : '');
        // Bug fix (audit r4): the provider tiles are <button role="radio"> rendered
        // OUTSIDE the provider card panel, so card.querySelector('input[type=radio]:checked')
        // would always be null and use_provider would always be '0'. The active provider is
        // tracked via the hidden <input name="provider"> (text AI) or <input name="image_source">
        // (image AI). We send use_provider=1 only when this card's provider matches the one the
        // user has currently selected as active — preserving the original UX intent.
        var providerKey = button.getAttribute('data-provider') || '';
        var kindAttr = button.getAttribute('data-test-kind') || 'text';
        var activeInput = document.querySelector(
            kindAttr === 'image' ? 'input[name="image_source"]' : 'input[name="provider"]'
        );
        var isActiveTab = !!(activeInput && activeInput.value && activeInput.value === providerKey);
        data.append('use_provider', isActiveTab ? '1' : '0');

        fetch(config('data-aiap-ajax-url', window.ajaxurl || ''), {
            method: 'POST',
            credentials: 'same-origin',
            body: data
        }).then(function (response) {
            return response.text().then(function (text) {
                var cleanText = text.replace(/^\uFEFF/, '').trim();
                var json = null;
                try {
                    json = JSON.parse(cleanText);
                } catch (e) {}
                return { status: response.status, json: json, text: cleanText };
            });
        }).then(function (payload) {
            var json = payload.json;
            var ok = json && json.success;
            var data = json && json.data ? json.data : null;
            var isWarning = !!(ok && data && data.warning);
            var message;
            if (ok) {
                if (isWarning && data && data.message) {
                    // 課金/クォータ系: キーは有効だが残高/上限の問題。警告メッセージを表示。
                    message = data.message;
                } else if (data && data.used_saved_key) {
                    // 入力欄が空 → DB保存済みキーを使ってテストした旨を明示
                    message = (window.AIAP_I18N && window.AIAP_I18N.ok_saved_key) ? window.AIAP_I18N.ok_saved_key : 'OK (saved key)';
                } else {
                    message = (window.AIAP_I18N && window.AIAP_I18N.ok) ? window.AIAP_I18N.ok : 'OK';
                }
            } else {
                var noRespFallback = (window.AIAP_I18N && window.AIAP_I18N.no_response) ? window.AIAP_I18N.no_response : 'No response';
                message = (data && data.message)
                    ? data.message
                    : ('HTTP ' + payload.status + ': ' + (payload.text ? payload.text.replace(/<[^>]*>/g, '').trim().slice(0, 120) : noRespFallback));
                // DB保存済みキーで失敗した場合、入力欄が空であった旨を明示する
                if (data && data.used_saved_key) {
                    var prefix = (window.AIAP_I18N && window.AIAP_I18N.saved_key_prefix) ? window.AIAP_I18N.saved_key_prefix : '[saved key]';
                    message = prefix + ' ' + message;
                }
            }
            showButtonResult(button, ok && !isWarning, message);

            // テスト成功 (および warning=quota_exhausted) 時: テスト済みリストを更新
            // (warning でもキー自体は有効なので、トグル切替を許可する)
            if (ok && data && data.provider) {
                if (verifiedProviders.indexOf(data.provider) === -1) {
                    verifiedProviders.push(data.provider);
                }
            }
        }).catch(function (error) {
            var fallback = (window.AIAP_I18N && window.AIAP_I18N.network_error) ? window.AIAP_I18N.network_error : 'Network error';
            showButtonResult(button, false, error && error.message ? error.message : fallback);
        });
    }

    // テストボタン: ローディング状態に切り替え(元のHTMLを保存)
    function setButtonLoading(button) {
        if (!button.hasAttribute('data-original-html')) {
            button.setAttribute('data-original-html', button.innerHTML);
        }
        // タイマーで元に戻すスケジュールがあればキャンセル
        if (button._aiapRestoreTimer) {
            clearTimeout(button._aiapRestoreTimer);
            button._aiapRestoreTimer = null;
        }
        button.disabled = true;
        button.classList.remove('aiap-btn-test-ok', 'aiap-btn-test-error');
        button.classList.add('aiap-btn-test-loading');
        button.innerHTML = '<span class="dashicons dashicons-update aiap-spin"></span> ...';
    }

    // テストボタン: 結果をボタン内に表示 → 数秒後に元に戻す
    function showButtonResult(button, ok, message) {
        button.classList.remove('aiap-btn-test-loading');
        button.classList.add(ok ? 'aiap-btn-test-ok' : 'aiap-btn-test-error');
        button.disabled = false;
        // 長いエラーは短く
        var shortMsg = message.length > 40 ? message.slice(0, 40) + '…' : message;
        var icon = ok ? 'dashicons-yes' : 'dashicons-no-alt';
        button.innerHTML = '<span class="dashicons ' + icon + '"></span> ' + escapeHtml(shortMsg);
        button.title = message; // 完全なメッセージはtooltipで
        // OKは3秒、エラーは6秒後に元の表示に戻す
        if (button._aiapRestoreTimer) { clearTimeout(button._aiapRestoreTimer); }
        button._aiapRestoreTimer = setTimeout(function () {
            var original = button.getAttribute('data-original-html');
            if (original) {
                button.innerHTML = original;
            }
            button.classList.remove('aiap-btn-test-ok', 'aiap-btn-test-error');
            button.title = '';
            button._aiapRestoreTimer = null;
        }, ok ? 3000 : 6000);
    }

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

    function initRunNow() {
        var form = document.querySelector('.aiap-run-now-form');
        if (!form) {
            return;
        }
        var button = form.querySelector('.aiap-run-now-btn');
        var textarea = form.querySelector('textarea[name="manual_prompt"]');
        var progress = form.querySelector('.aiap-progress');
        var resultBox = form.querySelector('.aiap-progress-result');
        var video = form.querySelector('.aiap-progress-video');
        var stepNodes = {
            ai: form.querySelector('.aiap-progress-step[data-step="ai"]'),
            post: form.querySelector('.aiap-progress-step[data-step="post"]'),
            image: form.querySelector('.aiap-progress-step[data-step="image"]'),
            seo: form.querySelector('.aiap-progress-step[data-step="seo"]')
        };
        var labels = {
            run: form.getAttribute('data-label-run') || 'Run',
            running: form.getAttribute('data-label-running') || 'Running…',
            ai: form.getAttribute('data-label-ai') || 'Generating…',
            post: form.getAttribute('data-label-post') || 'Creating post…',
            image: form.getAttribute('data-label-image') || 'Generating image…',
            seo: form.getAttribute('data-label-seo') || 'Applying SEO…',
            done: form.getAttribute('data-label-done') || 'Completed',
            error: form.getAttribute('data-label-error') || 'Error',
            promptRequired: form.getAttribute('data-label-prompt-required') || 'Prompt is required.',
            retryAi: form.getAttribute('data-label-retry-ai') || 'AI retries: %d',
            retryDedup: form.getAttribute('data-label-retry-dedup') || 'Dedup retries: %d',
            serverRetry: form.getAttribute('data-label-server-retry') || 'Recovering from server error %1$d/%2$d',
            dedupRetry: form.getAttribute('data-label-dedup-retry') || 'Resolving similar content %1$d/%2$d'
        };
        var maxRetryAi = parseInt(form.getAttribute('data-max-retry-ai') || '3', 10);
        var maxRetryDedup = parseInt(form.getAttribute('data-max-retry-dedup') || '3', 10);
        if (!(maxRetryAi >= 1)) { maxRetryAi = 3; }
        if (!(maxRetryDedup >= 1)) { maxRetryDedup = 3; }
        var ajaxUrl = config('data-aiap-ajax-url', window.ajaxurl || '');
        var nonce = config('data-aiap-nonce', '');

        function resetProgress() {
            Object.keys(stepNodes).forEach(function (k) {
                var node = stepNodes[k];
                if (!node) { return; }
                node.className = 'aiap-progress-step';
                node.setAttribute('data-step', k);
                node.textContent = '';
            });
            if (resultBox) {
                resultBox.className = 'aiap-progress-result';
                resultBox.textContent = '';
            }
            progress.hidden = false;
            progress.classList.remove('is-idle', 'is-done', 'is-error');
        }

        function playVideo() {
            if (!video) { return; }
            try {
                video.currentTime = 0;
            } catch (e) { /* ブラウザ次第で投げることがあるが致命的ではない */ }
            var p = video.play();
            // play() は Promise を返すブラウザがある。autoplay ポリシー違反等で reject されても無視
            if (p && typeof p.catch === 'function') { p.catch(function () {}); }
        }

        function stopVideo() {
            if (!video) { return; }
            try { video.pause(); } catch (e) {}
        }

        function markStep(step, state, text) {
            var node = stepNodes[step];
            if (!node) { return; }
            node.className = 'aiap-progress-step is-' + state;
            node.setAttribute('data-step', step);
            // text 省略時(undefined) は現状の表示テキストを維持する。
            // エラー時に「AIで記事を作成しています…」等の進捗メッセージを残したまま、
            // 状態クラス(is-error)だけを切り替える用途で使う。
            if (typeof text !== 'undefined') {
                node.textContent = text;
            }
        }

        function showResult(state, message, extra) {
            if (!resultBox) { return; }
            resultBox.className = 'aiap-progress-result is-' + state;
            resultBox.textContent = '';
            // 完了/エラー時は動画を止めて、CSS で薄く表示する
            stopVideo();
            progress.classList.remove('is-idle');
            if (state === 'error') {
                progress.classList.add('is-error');
            } else {
                progress.classList.add('is-done');
            }
            var msgSpan = document.createElement('span');
            msgSpan.textContent = message;
            resultBox.appendChild(msgSpan);
            if (extra && extra.editUrl && extra.postId) {
                resultBox.appendChild(document.createTextNode(' '));
                var link = document.createElement('a');
                link.href = extra.editUrl;
                link.textContent = '#' + extra.postId;
                resultBox.appendChild(link);
            }
            // Pro アップグレード CTA (上限到達時など)
            if (extra && extra.upgradeUrl) {
                var ctaWrap = document.createElement('div');
                ctaWrap.className = 'aiap-progress-upgrade';
                var ctaLink = document.createElement('a');
                ctaLink.className = 'aiap-pro-cta';
                ctaLink.href = extra.upgradeUrl;
                var icon = document.createElement('span');
                icon.className = 'dashicons dashicons-star-filled';
                ctaLink.appendChild(icon);
                ctaLink.appendChild(document.createTextNode(
                    ' ' + (form.getAttribute('data-label-upgrade') || 'Upgrade to Pro')
                ));
                ctaWrap.appendChild(ctaLink);
                resultBox.appendChild(ctaWrap);
            }
            if (extra && extra.warnings && extra.warnings.length) {
                extra.warnings.forEach(function (w) {
                    var warnDiv = document.createElement('div');
                    warnDiv.className = 'aiap-progress-warning';
                    warnDiv.textContent = w;
                    resultBox.appendChild(warnDiv);
                });
            }
        }

        function setBusy(busy) {
            button.disabled = busy;
            while (button.firstChild) { button.removeChild(button.firstChild); }
            var icon = document.createElement('span');
            icon.className = busy ? 'dashicons dashicons-update aiap-spin' : 'dashicons dashicons-controls-play';
            button.appendChild(icon);
            button.appendChild(document.createTextNode(' ' + (busy ? labels.running : labels.run)));
            if (busy) {
                playVideo();
            } else {
                stopVideo();
            }
        }

        function callAjax(params) {
            var body = new FormData();
            Object.keys(params).forEach(function (k) {
                // 配列値の場合は同じキーで複数 append（PHP 側で $_POST に配列として届くようにする）
                if (Array.isArray(params[k])) {
                    params[k].forEach(function (v) { body.append(k, v); });
                } else {
                    body.append(k, params[k]);
                }
            });
            body.append('nonce', nonce);
            return fetch(ajaxUrl, {
                method: 'POST',
                credentials: 'same-origin',
                body: body
            }).then(function (response) {
                return response.text().then(function (text) {
                    var cleaned = text.replace(/^\uFEFF/, '').trim();
                    var json = null;
                    try { json = JSON.parse(cleaned); } catch (e) {}
                    return { status: response.status, json: json, text: cleaned };
                });
            });
        }

        function runStep(step, warnings) {
            var stepLabel = labels[step] || step;
            markStep(step, 'running', stepLabel);
            return callAjax({ action: 'aiap_run_now_step', step: step }).then(function (payload) {
                var json = payload.json;
                if (!json || !json.success) {
                    var msg = (json && json.data && json.data.message) ? json.data.message : ('HTTP ' + payload.status);
                    // 進捗ステップ表示・結果バナーの描画は catch ハンドラに集約する。
                    // ここで markStep / showResult を呼ぶと catch 側でも同じメッセージが描かれ、
                    // 同じエラー文が画面に二重に出る(支出上限超過時のステップ行+バナー重複の原因)。
                    var stepErr = new Error(msg);
                    stepErr.aiapErrorStep = step;
                    stepErr.aiapKind      = (json && json.data && json.data.kind) ? json.data.kind : '';
                    throw stepErr;
                }
                markStep(step, 'done', stepLabel);
                if (json.data && json.data.warnings) {
                    json.data.warnings.forEach(function (w) {
                        if (warnings.indexOf(w) === -1) { warnings.push(w); }
                    });
                }
                var next = json.data && json.data.next_step;
                if (next && next !== 'done') {
                    return runStep(next, warnings);
                }
                return { data: json.data, warnings: warnings };
            });
        }

        form.addEventListener('submit', function (event) {
            event.preventDefault();
            var prompt = textarea ? textarea.value.trim() : '';
            // プロンプト空欄はサーバー側で「次の予定タスク」から取得するため許容
            setBusy(true);
            resetProgress();

            var langInput = form.querySelector('select[name="manual_content_language"]');
            var lenInput = form.querySelector('input[name="manual_content_length"]');
            var imageModeInput = form.querySelector('input[name="manual_featured_image_mode"]:checked');
            var startParams = { action: 'aiap_run_now_start', manual_prompt: prompt };
            if (langInput) { startParams.manual_content_language = langInput.value; }
            if (lenInput) { startParams.manual_content_length = lenInput.value; }
            if (imageModeInput) { startParams.manual_featured_image_mode = imageModeInput.value; }

            // RSS 関連フィールド（Quick Post の RSS Settings セクションから収集）
            // ここで明示的にコピーしないと callAjax の body に含まれず、サーバー側で RSS が使われない。
            var rssFormFlag = form.querySelector('input[name="manual_rss_form_submitted"]');
            if (rssFormFlag) { startParams.manual_rss_form_submitted = rssFormFlag.value; }
            var rssEnabledInput = form.querySelector('input[name="manual_rss_enabled"]');
            if (rssEnabledInput && rssEnabledInput.checked) {
                startParams.manual_rss_enabled = '1';
            }
            var rssCountInput = form.querySelector('input[name="manual_rss_count"]');
            if (rssCountInput && rssCountInput.value !== '') {
                startParams.manual_rss_count = rssCountInput.value;
            }
            var rssModeInput = form.querySelector('input[name="manual_rss_mode"]:checked');
            if (rssModeInput) { startParams.manual_rss_mode = rssModeInput.value; }
            var rssFallbackInput = form.querySelector('input[name="manual_rss_fallback"]:checked');
            if (rssFallbackInput) { startParams.manual_rss_fallback = rssFallbackInput.value; }
            var rssFeedInputs = form.querySelectorAll('input[name="manual_rss_feed_ids[]"]:checked');
            if (rssFeedInputs.length > 0) {
                var feedIds = [];
                rssFeedInputs.forEach(function (cb) { feedIds.push(cb.value); });
                startParams['manual_rss_feed_ids[]'] = feedIds;
            }

            var warnings = [];
            markStep('ai', 'running', labels.ai);

            // サーバエラー(レート制限・サーバ一時障害)で連続リトライした回数。
            // サーバ側でリトライしないので JS が制御。各サイクルでサーバ側の dedup_attempts と独立してカウント。
            var serverRetryCount = 0;

            // 「これから入るリトライが何回目か」を表示する。
            // サーバー側の dedup_attempts は、リトライが必要だと確定した時点で ++ してから JS に返ってくる。
            // serverRetryCount は JS 側で「サーバエラーが返るたびに ++」する。両方とも値=これから入るリトライ回数。
            // 0 のとき(まだ一度も失敗していない初回試行中)は何も表示しない。
            //
            // Bug fix (audit r9 self-audit, #37): server retry の分母を「試行最大回数」
            // (= GENERATE_MAX_RETRY + 1 = 4) にする。旧実装は分母 = MAX_RETRY (=3) で
            // 「3/3」表示時にユーザーが「リトライ終了」と読むが、実際はその後 4 回目試行
            // (=最後のリトライ) が残っている、という UI 混乱があった。PHP のログ側
            // (log_server_error_retry の引数) も同じく MAX_RETRY+1 に変更済み。
            // dedup_retry はリトライ概念が異なる (3 回類似回避したら諦める) ので
            // 分母は MAX_RETRY のまま (修正対象外)。
            function fmtAttempt(tmpl, n, max) {
                return tmpl.replace('%1$d', n).replace('%2$d', max);
            }
            function formatAiLabelWithRetry(d) {
                var parts = [];
                var server = serverRetryCount > 0 ? serverRetryCount : 0;
                var dedup = (d && d.dedup_attempts > 0) ? d.dedup_attempts : 0;
                if (server > 0) {
                    var maxAttemptsAi = maxRetryAi + 1; // 試行最大回数 (初回 + リトライ N 回)
                    parts.push(fmtAttempt(labels.serverRetry, Math.min(server, maxAttemptsAi), maxAttemptsAi));
                }
                if (dedup > 0) {
                    parts.push(fmtAttempt(labels.dedupRetry, Math.min(dedup, maxRetryDedup), maxRetryDedup));
                }
                return parts.length ? labels.ai + ' (' + parts.join(', ') + ')' : labels.ai;
            }

            // retry_needed のループ: 各リトライを別Ajaxで投げる(Cloudflareタイムアウト回避)
            function runAiLoop(params) {
                // server_retry_count: PHP 側でログラベル(N/M)に使うため、毎回現在値を送る
                var sendParams = {};
                if (params) {
                    for (var k in params) {
                        if (Object.prototype.hasOwnProperty.call(params, k)) {
                            sendParams[k] = params[k];
                        }
                    }
                }
                sendParams.server_retry_count = serverRetryCount;
                return callAjax(sendParams).then(function (payload) {
                    var json = payload.json;
                    if (!json || !json.success) {
                        var errData = (json && json.data) ? json.data : {};

                        // サーバエラーで retryable=true の場合は、JS 側でリトライ判定する
                        // (画面に「サーバ障害対応中 N/M」進捗を表示するため)。
                        // 上限判定は PHP 側で行い、上限超過時は retryable=false に書き換えて返す。
                        if (errData.retryable === true) {
                            serverRetryCount++;
                            // 表示更新: 「サーバ障害対応中 N/M」(dedup の値は維持して両方併記)
                            markStep('ai', 'running', formatAiLabelWithRetry({
                                dedup_attempts: (params && params.dedup_attempts) || 0
                            }));
                            // Bug fix (audit r9 self-audit, #35): リトライ前の待機。
                            // 待機秒数の計算は PHP 側 (AIAP_AI_Client::compute_retry_sleep_seconds)
                            // が単一の真実の供給源。レスポンスの next_sleep_seconds をそのまま使う。
                            // 万一 PHP が値を含めずに返してきた場合のフォールバックは 5 秒。
                            var nextSleepSec = (errData.next_sleep_seconds && errData.next_sleep_seconds > 0)
                                ? errData.next_sleep_seconds : 5;
                            return new Promise(function (resolve) {
                                setTimeout(resolve, nextSleepSec * 1000);
                            }).then(function () {
                                return runAiLoop(params);
                            });
                        }

                        // ここに来るのは: 永続エラー(retryable=false) / リトライ上限到達 / その他のエラー
                        var msg;
                        if (errData.message) {
                            msg = errData.message;
                        } else if (payload.status >= 500) {
                            // 500系: PHP Fatal Error等でJSONが返っていない。レスポンス本文の先頭を見せる
                            var body = (payload.text || '').replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim().slice(0, 200);
                            var serverErrLabel = form.getAttribute('data-label-server-error') || (window.AIAP_I18N && window.AIAP_I18N.server_error ? window.AIAP_I18N.server_error : 'HTTP %d');
                            msg = serverErrLabel.replace('%d', payload.status) + (body ? ': ' + body : '');
                        } else {
                            msg = 'HTTP ' + payload.status;
                        }
                        var err = new Error(msg);
                        // Free 上限到達などの特殊ケース用にメタデータを保持
                        err.aiapStep      = errData.step    || '';
                        err.aiapErrorStep = 'ai';                  // 進捗UI上のエラー対象ステップ
                        err.aiapKind      = errData.kind    || ''; // PHP classify_api_error 由来 (quota_exhausted / auth_error 等)
                        err.aiapUpgrade   = errData.upgrade || '';
                        err.aiapLimit     = errData.limit   || 0;
                        err.aiapCount     = errData.count   || 0;
                        throw err;
                    }
                    var data = json.data || {};
                    // リトライが必要ならUIを更新してもう一度投げる(dedup リトライ)
                    if (data.retry_needed) {
                        // dedup リトライに入る瞬間は serverRetryCount をリセット
                        // (dedup ループ単位でサーバエラー試行回数は仕切り直す)
                        serverRetryCount = 0;
                        markStep('ai', 'running', formatAiLabelWithRetry(data));
                        return runAiLoop({
                            action: 'aiap_run_now_retry',
                            attempt: data.next_attempt,
                            retry_prompt: data.retry_prompt,
                            dedup_attempts: data.dedup_attempts
                        });
                    }
                    // 完了時は試行回数を残さずベースラベルのみ表示する。
                    // (試行中の途中経過はリトライ開始時の running 表示で見せる。)
                    markStep('ai', 'done', labels.ai);
                    var next = data.next_step;
                    if (next && next !== 'done') {
                        return runStep(next, warnings);
                    }
                    return { data: data, warnings: warnings };
                });
            }

            runAiLoop(startParams).then(function (final) {
                showResult('done', labels.done + ': ' + (final.data.message || ''), {
                    postId: final.data.post_id,
                    editUrl: final.data.edit_url,
                    warnings: final.warnings
                });
            }).catch(function (err) {
                var msg = err && err.message ? err.message : 'Error';
                // ステップ行は一切触らない: 進行中メッセージ(「AIで記事を生成しています…」等)を
                // テキストも状態クラスもそのまま維持する。
                // 失敗の通知は「進行中ステップ行の真下にエラー行を追加表示する」形で行う。
                // (.aiap-progress-result.is-error は CSS でステップ行のエラー形式と揃えてある: ❌ + 赤 + 太字)
                if (err && err.aiapStep === 'limit') {
                    // Free プランの月次上限到達は専用 UI(アップグレード CTA 付きバナー)
                    showResult('limit', msg, { upgradeUrl: err.aiapUpgrade });
                    return;
                }
                showResult('error', labels.error + ': ' + msg);
            }).then(function () {
                setBusy(false);
            });
        });
    }

    /**
     * スタイルselectを新しい選択肢で差し替える共通ヘルパー
     * @param {Element} card     対象プロバイダカード要素
     * @param {string}  provider プロバイダキー
     * @param {Array}   options  [ { value, label }, ... ]
     * @param {string}  current  選択しておくべき値
     */
    function applyStyleUpdate(card, provider, options, current) {
        var wrapper = card.querySelector('[data-style-wrapper][data-provider="' + provider + '"]');
        if (!wrapper) { return; }
        var styleSelect = wrapper.querySelector('.aiap-style-select[data-provider="' + provider + '"]');
        var notice = wrapper.querySelector('[data-style-notice]');
        var noticeText = notice ? notice.querySelector('[data-style-notice-text]') : null;

        if (!options || options.length === 0) {
            // このモデルはスタイル非対応 -> 欄ごと非表示にする
            wrapper.hidden = true;
            return;
        }
        wrapper.hidden = false;

        if (!styleSelect) { return; }

        // 現在選択されている値を覚えておき、新リストに無い場合は警告を出しつつ先頭値にフォールバック
        var previousValue = styleSelect.value;
        var valueSet = options.map(function (o) { return o.value; });
        var unsupportedValue = '';
        if (previousValue && valueSet.indexOf(previousValue) === -1) {
            unsupportedValue = previousValue;
        }

        while (styleSelect.firstChild) { styleSelect.removeChild(styleSelect.firstChild); }
        options.forEach(function (o) {
            var opt = document.createElement('option');
            opt.value = o.value;
            opt.textContent = o.label;
            styleSelect.appendChild(opt);
        });

        // 選択値を決定: サーバー指定のcurrent優先、なければ以前の値(サポート内なら)、なければ先頭
        var chosen = '';
        if (current && valueSet.indexOf(current) !== -1) {
            chosen = current;
        } else if (previousValue && valueSet.indexOf(previousValue) !== -1) {
            chosen = previousValue;
        } else {
            chosen = options[0].value;
        }
        styleSelect.value = chosen;

        // 「前に選ばれていた値がサポート外になった」場合のみ警告表示
        if (notice) {
            if (unsupportedValue) {
                var tpl = (window.AIAP_STYLE_MATRIX && window.AIAP_STYLE_MATRIX.unsupported_text) || '%s is not supported by the current model.';
                var msg = tpl.replace('%s', unsupportedValue);
                if (noticeText) {
                    noticeText.textContent = msg;
                } else {
                    // テキスト専用spanが無い旧DOMの場合、notice自体のテキストを差し替え
                    notice.textContent = '';
                    var icon = document.createElement('span');
                    icon.className = 'dashicons dashicons-warning';
                    notice.appendChild(icon);
                    notice.appendChild(document.createTextNode(' ' + msg));
                }
                notice.hidden = false;
            } else {
                notice.hidden = true;
                if (noticeText) { noticeText.textContent = ''; }
            }
        }
    }

    /**
     * 画像プロバイダのモデルselectが変更された瞬間、サーバーに問い合わせて
     * そのモデルに対応するスタイル欄を動的に更新する
     */
    function initModelChangeStyleSync() {
        var IMAGE_PROVIDERS = ['dalle3', 'imagen', 'stability', 'flux'];

        document.addEventListener('change', function (event) {
            var select = event.target;
            if (!select || !select.classList || !select.classList.contains('aiap-model-select')) { return; }
            var provider = select.getAttribute('data-provider') || '';
            if (IMAGE_PROVIDERS.indexOf(provider) === -1) { return; }

            var card = select.closest('.aiap-image-provider-card');
            if (!card) { return; }

            var model = select.value;
            var body = new FormData();
            body.append('action', 'aiap_styles_for_model');
            body.append('nonce', config('data-aiap-nonce', ''));
            body.append('provider', provider);
            body.append('model', model);

            fetch(config('data-aiap-ajax-url', window.ajaxurl || ''), {
                method: 'POST',
                credentials: 'same-origin',
                body: body
            }).then(function (r) {
                return r.text().then(function (txt) {
                    var json = null;
                    try { json = JSON.parse(txt.replace(/^\uFEFF/, '').trim()); } catch (e) {}
                    return json;
                });
            }).then(function (json) {
                if (!json || !json.success) { return; }
                var data = json.data || {};
                applyStyleUpdate(card, provider, data.styles || [], '');
            }).catch(function () {
                // ネットワークエラーは無視(次回保存時にサーバー側検証が効くため致命的ではない)
            });
        });
    }

    // ========== 「使用するプロバイダ」トグルガード ==========
    // テスト未済のプロバイダをONにしたら、トグルをOFFに戻して通知を出す

    var verifiedProviders = [];
    try {
        var rootEl = document.querySelector('.aiap-wrap');
        if (rootEl) {
            verifiedProviders = JSON.parse(rootEl.getAttribute('data-aiap-verified') || '[]');
        }
    } catch (e) {
        verifiedProviders = [];
    }

    function isProviderVerified(provider) {
        return verifiedProviders.indexOf(provider) !== -1;
    }

    function initProviderToggleGuard() {
        document.addEventListener('change', function (e) {
            var radio = e.target;
            if (!radio || radio.type !== 'radio') return;
            if (radio.name !== 'provider' && radio.name !== 'image_source') return;
            if (!radio.checked) return;
            if (isProviderVerified(radio.value)) return;
            // 元々ONだったラジオに戻す
            var radios = document.querySelectorAll('input[type="radio"][name="' + radio.name + '"]');
            radios.forEach(function (r) { r.checked = r.defaultChecked; });
            alert(config('data-aiap-not-tested-msg', 'Please test the API key first.'));
        });
    }

    document.addEventListener('DOMContentLoaded', function () {
        initRadioPills();
        initCardSelects();
        initImageSettings();
        initToggles();
        initConfirmForms();
        initApiControls();
        initEditTaskButtons();
        initRefreshModelsButtons();
        initModelChangeStyleSync();
        initAddTaskToggle();
        initCancelEditTask();
        initRunNow();
        initDedupSensitivity();
        initEnabledToggle();
        initProviderToggleGuard();
        initRandomMode();
        initSidebarToggle();
        initWizard();
        initQuickPostCharCount();
    });

    // Phase 6: Quick Post の文字数カウンター
    function initQuickPostCharCount() {
        var ta = document.getElementById('js-aiap-manual-prompt');
        var cnt = document.querySelector('[data-aiap-charcount]');
        if (!ta || !cnt) return;
        function update() {
            var n = ta.value.length;
            cnt.textContent = n;
            cnt.classList.toggle('is-active', n > 0);
        }
        ta.addEventListener('input', update);
        update();
    }

    // Phase 3: Quick Start ウィザード (4 ステップ + 進捗バー + プリセット + レビュー反映)
    function initWizard() {
        var form = document.querySelector('[data-aiap-wizard-form]');
        if (!form) return;

        var TOTAL = 4;
        var current = 1;

        function show(step) {
            current = Math.max(1, Math.min(TOTAL, step));
            var steps = form.querySelectorAll('[data-aiap-wizard-step]');
            steps.forEach(function (s) {
                s.classList.toggle('is-active', parseInt(s.getAttribute('data-aiap-wizard-step'), 10) === current);
            });
            var fill = document.querySelector('[data-aiap-wizard-progress-fill]');
            if (fill) fill.style.width = (current / TOTAL * 100) + '%';
            var nums = document.querySelectorAll('[data-stepnum]');
            nums.forEach(function (n) {
                var v = parseInt(n.getAttribute('data-stepnum'), 10);
                n.classList.remove('is-active', 'is-done');
                if (v < current) n.classList.add('is-done');
                else if (v === current) n.classList.add('is-active');
            });
            if (current === TOTAL) updateReview();
            try { form.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) {}
        }

        function validateCurrent() {
            if (current === 1) {
                var p = form.querySelector('#aiap-wiz-prompt');
                if (!p || !p.value.trim()) {
                    if (p) p.focus();
                    return false;
                }
            }
            return true;
        }

        function updateReview() {
            var review = document.querySelector('[data-aiap-wizard-review]');
            if (!review) return;
            var p = (form.querySelector('#aiap-wiz-prompt') || {}).value || '';
            var freqInput = form.querySelector('input[name="wiz_freq"]:checked');
            var freqLabel = freqInput ? ((freqInput.parentNode.querySelector('.aiap-wizard-option-name') || {}).textContent || '') : '';
            var prov = (form.querySelector('#aiap-wiz-provider') || {}).value || '';
            var provText = prov === 'anthropic' ? 'Anthropic (Claude)' : (prov === 'openai' ? 'OpenAI (GPT)' : (prov === 'gemini' ? 'Google (Gemini)' : prov));
            var apik = (form.querySelector('#aiap-wiz-apikey') || {}).value || '';
            var apikText = apik ? (apik.substring(0, 4) + '••••••••') : '—';

            var setReview = function (key, text) {
                var el = review.querySelector('[data-review="' + key + '"]');
                if (el) el.textContent = text;
            };
            setReview('prompt', p.length > 80 ? p.substring(0, 80) + '…' : p);
            setReview('freq', (freqLabel || '').trim());
            setReview('provider', provText);
            setReview('apikey', apikText);
        }

        form.addEventListener('click', function (e) {
            if (e.target.matches('[data-aiap-wizard-next]')) {
                e.preventDefault();
                if (validateCurrent()) show(current + 1);
            } else if (e.target.matches('[data-aiap-wizard-prev]')) {
                e.preventDefault();
                show(current - 1);
            }
        });

        form.addEventListener('click', function (e) {
            var btn = e.target.closest('[data-aiap-wizard-preset]');
            if (!btn) return;
            e.preventDefault();
            var v = btn.getAttribute('data-aiap-wizard-preset') || '';
            var p = form.querySelector('#aiap-wiz-prompt');
            if (p) {
                p.value = v;
                p.focus();
            }
            form.querySelectorAll('[data-aiap-wizard-preset]').forEach(function (b) {
                b.classList.remove('is-selected');
            });
            btn.classList.add('is-selected');
        });

        show(1);
    }

    // モバイル用サイドバーのハンバーガー トグル (#nav-redesign)
    function initSidebarToggle() {
        document.addEventListener('click', function (e) {
            var t = e.target.closest('[data-aiap-sidebar-toggle]');
            if (!t) return;
            e.preventDefault();
            var sidebar = document.querySelector('[data-aiap-sidebar]');
            if (!sidebar) return;
            var isOpen = sidebar.classList.toggle('is-open');
            t.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
        });

        // サイドバー外クリックで閉じる (モバイルのみ)
        document.addEventListener('click', function (e) {
            if (window.innerWidth > 768) return;
            var sidebar = document.querySelector('[data-aiap-sidebar]');
            if (!sidebar || !sidebar.classList.contains('is-open')) return;
            if (sidebar.contains(e.target)) return;
            if (e.target.closest('[data-aiap-sidebar-toggle]')) return;
            sidebar.classList.remove('is-open');
            var t = document.querySelector('[data-aiap-sidebar-toggle]');
            if (t) t.setAttribute('aria-expanded', 'false');
        });
    }

    // ランダム投稿モード UI (#47): ON/OFF 切替で表示を切り替え + 各行内の追加/削除
    function initRandomMode() {
        // 表示切替: data-aiap-random-toggle のチェック状態で random-fields/schedule-fields を切替
        document.addEventListener('change', function (e) {
            var t = e.target;
            if (!t || t.getAttribute('data-aiap-random-toggle') !== '1') return;
            var form = t.closest('form');
            if (!form) return;
            var randomFields   = form.querySelector('[data-aiap-random-fields]');
            var scheduleFields = form.querySelector('[data-aiap-schedule-fields]');
            if (t.checked) {
                if (randomFields)   { randomFields.style.display = ''; }
                if (scheduleFields) { scheduleFields.style.display = 'none'; }
            } else {
                if (randomFields)   { randomFields.style.display = 'none'; }
                if (scheduleFields) { scheduleFields.style.display = ''; }
            }
        });

        // 許可時間帯 (Phase 13): 各行内の「+ 追加」 → 自分の行の直下に新しい行を挿入
        document.addEventListener('click', function (e) {
            var t = e.target;
            if (!t || !t.matches('[data-aiap-allowed-add]')) return;
            e.preventDefault();
            var row = t.closest('[data-aiap-allowed-row]');
            if (!row) return;
            var list = row.parentNode;
            // index は現存数を使う (重複しても sanitize で問題なし、見た目のみ)
            var idx = list.children.length;
            var newRow = document.createElement('div');
            newRow.className = 'aiap-allowed-row';
            newRow.setAttribute('data-aiap-allowed-row', '');
            newRow.innerHTML =
                '<input type="time" name="task_allowed_periods[' + idx + '][start]" step="60" class="aiap-input-text">'
                + '<span class="aiap-counts-sep">–</span>'
                + '<input type="time" name="task_allowed_periods[' + idx + '][end]" step="60" class="aiap-input-text">'
                + '<div class="aiap-allowed-actions">'
                + '<button type="button" class="button" data-aiap-allowed-remove>' + (window.AIAP_I18N && AIAP_I18N.remove ? AIAP_I18N.remove : 'Remove') + '</button>'
                + '<button type="button" class="button" data-aiap-allowed-add>' + (window.AIAP_I18N && AIAP_I18N.add ? AIAP_I18N.add : '+ Add') + '</button>'
                + '</div>';
            list.insertBefore(newRow, row.nextSibling);
        });

        // 許可時間帯: 0 行のとき表示される「+ 時間帯を追加」 → 1 行追加 + 自分を隠す
        document.addEventListener('click', function (e) {
            var t = e.target;
            if (!t || !t.matches('[data-aiap-allowed-add-empty]')) return;
            e.preventDefault();
            var field = t.closest('.aiap-field');
            if (!field) return;
            var list = field.querySelector('[data-aiap-allowed-list]');
            if (!list) return;
            var idx = list.children.length;
            var newRow = document.createElement('div');
            newRow.className = 'aiap-allowed-row';
            newRow.setAttribute('data-aiap-allowed-row', '');
            newRow.innerHTML =
                '<input type="time" name="task_allowed_periods[' + idx + '][start]" step="60" class="aiap-input-text">'
                + '<span class="aiap-counts-sep">–</span>'
                + '<input type="time" name="task_allowed_periods[' + idx + '][end]" step="60" class="aiap-input-text">'
                + '<div class="aiap-allowed-actions">'
                + '<button type="button" class="button" data-aiap-allowed-remove>' + (window.AIAP_I18N && AIAP_I18N.remove ? AIAP_I18N.remove : 'Remove') + '</button>'
                + '<button type="button" class="button" data-aiap-allowed-add>' + (window.AIAP_I18N && AIAP_I18N.add ? AIAP_I18N.add : '+ Add') + '</button>'
                + '</div>';
            list.appendChild(newRow);
            t.style.display = 'none';
        });

        // 許可時間帯: 「削除」 → その行を削除 (0 行になったら「+ 時間帯を追加」を表示)
        document.addEventListener('click', function (e) {
            var t = e.target;
            if (!t || !t.matches('[data-aiap-allowed-remove]')) return;
            e.preventDefault();
            var row = t.closest('[data-aiap-allowed-row]');
            if (!row) return;
            var list = row.parentNode;
            list.removeChild(row);
            if (list.children.length === 0) {
                var field = list.closest('.aiap-field');
                var emptyBtn = field ? field.querySelector('[data-aiap-allowed-add-empty]') : null;
                if (emptyBtn) emptyBtn.style.display = '';
            }
        });
    }

    // 判定基準の3ボタン型ラジオ: クリックでアクティブ表示を切り替え
    function initDedupSensitivity() {
        document.querySelectorAll('.aiap-dedup-sens-group').forEach(function (group) {
            group.addEventListener('click', function (e) {
                var label = e.target.closest('.aiap-dedup-sens-option');
                if (!label) { return; }
                var radio = label.querySelector('input[type="radio"]');
                if (!radio) { return; }
                radio.checked = true;
                group.querySelectorAll('.aiap-dedup-sens-option').forEach(function (el) {
                    el.classList.remove('is-active');
                });
                label.classList.add('is-active');
            });
        });
    }

    // ============================================================
    // ダッシュボードの「自動投稿」ON/OFF トグル
    // ============================================================
    function initEnabledToggle() {
        var enabledToggle = document.getElementById('js-aiap-toggle-enabled');
        if (!enabledToggle) { return; }
        enabledToggle.addEventListener('change', function () {
            var on = enabledToggle.checked ? 1 : 0;
            var nonce = enabledToggle.getAttribute('data-nonce') || '';
            var labelEl = document.getElementById('js-aiap-toggle-label');
            var statusEl = document.getElementById('js-aiap-toggle-status');

            // 保存中は二重操作防止
            enabledToggle.disabled = true;
            if (statusEl) { statusEl.textContent = ''; }

            var body = new FormData();
            body.append('action', 'aiap_toggle_enabled');
            body.append('nonce', nonce);
            body.append('on', String(on));

            fetch(window.ajaxurl || '/wp-admin/admin-ajax.php', {
                method: 'POST',
                credentials: 'same-origin',
                body: body
            }).then(function (r) {
                return r.text().then(function (txt) {
                    var json = null;
                    try { json = JSON.parse(txt.replace(/^\uFEFF/, '').trim()); } catch (e) {}
                    return { status: r.status, json: json };
                });
            }).then(function (payload) {
                var json = payload.json;
                if (!json || !json.success) {
                    enabledToggle.checked = !on;
                    if (statusEl) { statusEl.textContent = (json && json.data && json.data.message) ? json.data.message : ('HTTP ' + payload.status); }
                    return;
                }
                var data = json.data || {};
                if (labelEl && data.label) {
                    // textContent + DOM API 経由で確実に XSS を回避する
                    while (labelEl.firstChild) { labelEl.removeChild(labelEl.firstChild); }
                    var strong = document.createElement('strong');
                    strong.textContent = data.label;
                    labelEl.appendChild(strong);
                }
                if (statusEl) {
                    statusEl.textContent = data.msg || '';
                    setTimeout(function () { statusEl.textContent = ''; }, 2000);
                }
            }).catch(function () {
                enabledToggle.checked = !on;
                if (statusEl) { statusEl.textContent = (window.AIAP_I18N && window.AIAP_I18N.network_error) ? window.AIAP_I18N.network_error : 'Network error'; }
            }).finally(function () {
                enabledToggle.disabled = false;
            });
        });
    }
}());

/* ============================================
   画像設定 — プリセット選択 / 詳細パネル
   ============================================ */
(function () {
    'use strict';
    var presetEls = document.querySelectorAll('[data-aiap-preset]');
    if (!presetEls.length) return;

    var hidden    = document.getElementById('aiap-image-preset');
    var labelEl   = document.getElementById('aiap-current-preset-label');
    var fmtEl     = document.getElementById('aiap-image-format');
    var qualEl    = document.getElementById('aiap-image-quality');
    var widthEl   = document.getElementById('aiap-image-max-width');
    var compEl    = document.getElementById('aiap-image-compress');
    var advanced  = document.getElementById('aiap-imgset-advanced');
    var advToggle = document.getElementById('aiap-imgset-advanced-toggle');

    function setPreset(btn) {
        presetEls.forEach(function (b) {
            b.classList.remove('is-selected');
            b.setAttribute('aria-checked', 'false');
        });
        btn.classList.add('is-selected');
        btn.setAttribute('aria-checked', 'true');
        if (hidden)  { hidden.value = btn.getAttribute('data-aiap-preset'); }
        if (labelEl) {
            var nameEl = btn.querySelector('.aiap-imgset-card__name');
            if (nameEl) { labelEl.textContent = nameEl.textContent; }
        }
        if (fmtEl)   { fmtEl.value   = btn.getAttribute('data-aiap-format');    }
        if (qualEl)  { qualEl.value  = btn.getAttribute('data-aiap-quality');   }
        if (widthEl) { widthEl.value = btn.getAttribute('data-aiap-max-width'); }
        if (compEl)  { compEl.value  = btn.getAttribute('data-aiap-compress');  }
    }

    presetEls.forEach(function (btn) {
        btn.addEventListener('click', function () { setPreset(btn); });
    });

    function markCustom() {
        presetEls.forEach(function (b) {
            b.classList.remove('is-selected');
            b.setAttribute('aria-checked', 'false');
        });
        if (hidden)  { hidden.value = 'custom'; }
        if (labelEl) { labelEl.textContent = labelEl.getAttribute('data-custom-label') || 'Custom'; }
    }
    [fmtEl, qualEl, widthEl, compEl].forEach(function (el) {
        if (!el) return;
        el.addEventListener('change', markCustom);
        el.addEventListener('input', markCustom);
    });

    if (advToggle && advanced) {
        advToggle.addEventListener('click', function () {
            var open = advanced.classList.toggle('is-open');
            advToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
        });
    }
}());

/* ============================================
   Provider Selection - タイル選択 → パネル切替 + active 設定
   ============================================ */
(function () {
    'use strict';
    var popular = document.getElementById('aiap-providers-popular');
    if (!popular) return;

    var allTiles = document.querySelectorAll('[data-aiap-provider]');
    var allPanels = document.querySelectorAll('[data-aiap-panel-for]');
    var activeInput = document.getElementById('aiap-active-provider-input');
    var tabInput = document.getElementById('aiap-provider-tab');

    function selectProvider(tile) {
        var key = tile.getAttribute('data-aiap-provider');
        if (!key) return;

        // タイルの選択状態を切り替え
        allTiles.forEach(function (t) {
            t.classList.remove('is-selected');
            t.setAttribute('aria-checked', 'false');
        });
        tile.classList.add('is-selected');
        tile.setAttribute('aria-checked', 'true');

        // 「使用するプロバイダ」hidden input
        if (activeInput) activeInput.value = key;
        // タブ位置(リダイレクト先)も同期
        if (tabInput) tabInput.value = key;

        // パネル表示切替
        allPanels.forEach(function (p) {
            if (p.getAttribute('data-aiap-panel-for') === key) {
                p.removeAttribute('hidden');
            } else {
                p.setAttribute('hidden', '');
            }
        });
    }

    allTiles.forEach(function (tile) {
        tile.addEventListener('click', function () {
            selectProvider(tile);
        });
    });
})();

/* ============================================
   Image Provider tile selection
   ============================================ */
(function () {
    'use strict';
    var popular = document.getElementById('aiap-image-providers-popular');
    if (!popular) return;

    var allTiles = document.querySelectorAll('[data-aiap-image-source]');
    var allPanels = document.querySelectorAll('[data-aiap-image-panel-for]');
    var activeInput = document.getElementById('aiap-active-image-source-input');
    var tabInput = document.getElementById('aiap-image-tab');

    function selectImageSource(tile) {
        var key = tile.getAttribute('data-aiap-image-source');
        if (!key) return;
        allTiles.forEach(function (t) {
            t.classList.remove('is-selected');
            t.setAttribute('aria-checked', 'false');
        });
        tile.classList.add('is-selected');
        tile.setAttribute('aria-checked', 'true');
        if (activeInput) activeInput.value = key;
        if (tabInput) tabInput.value = key;
        allPanels.forEach(function (p) {
            if (p.getAttribute('data-aiap-image-panel-for') === key) {
                p.removeAttribute('hidden');
            } else {
                p.setAttribute('hidden', '');
            }
        });
    }

    allTiles.forEach(function (tile) {
        tile.addEventListener('click', function () {
            selectImageSource(tile);
        });
    });
})();

/* ============================================
   Segment control (sensitivity radio)
   ============================================ */
(function () {
    'use strict';
    document.querySelectorAll('.aiap-segment').forEach(function (seg) {
        var opts = seg.querySelectorAll('.aiap-segment-option');
        opts.forEach(function (opt) {
            var input = opt.querySelector('input[type=radio]');
            if (!input) return;
            input.addEventListener('change', function () {
                opts.forEach(function (o) { o.classList.remove('is-active'); });
                if (input.checked) opt.classList.add('is-active');
            });
        });
    });
})();

/* ============================================
   RSS feed handlers
   ============================================ */
(function () {
    'use strict';

    // 共通: AJAX POST
    function aiapRssPost(action, payload, onSuccess, onError) {
        var body = new URLSearchParams();
        body.set('action', action);
        var nonce = document.querySelector('input[name="_wpnonce"]');
        if (nonce) { body.set('_ajax_nonce', nonce.value); }
        Object.keys(payload || {}).forEach(function (k) {
            body.set(k, payload[k]);
        });
        fetch(window.ajaxurl, {
            method: 'POST',
            credentials: 'same-origin',
            body: body
        }).then(function (r) { return r.json(); })
          .then(function (json) {
              if (json && json.success) {
                  if (onSuccess) { onSuccess(json.data || {}); }
              } else {
                  if (onError) { onError((json && json.data && json.data.message) || 'Unknown error'); }
              }
          })
          .catch(function (e) {
              if (onError) { onError(String(e)); }
          });
    }

    // 接続テスト
    document.querySelectorAll('.js-aiap-rss-test').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            var url = btn.getAttribute('data-url');
            if (!url) {
                var form = btn.closest('form');
                var urlInput = form ? form.querySelector('input[name="url"]') : null;
                url = urlInput ? urlInput.value : '';
            }
            var scope = btn.closest('.aiap-rss-feed-card') || btn.closest('form') || document;
            var resultEl = scope.querySelector('.aiap-rss-test-result');
            if (resultEl) {
                resultEl.textContent = '...';
                resultEl.classList.remove('is-success', 'is-error');
            }
            aiapRssPost('aiap_test_rss_feed', { url: url },
                function (data) {
                    if (resultEl) {
                        resultEl.textContent = data.message || 'OK';
                        resultEl.classList.add('is-success');
                    }
                },
                function (msg) {
                    if (resultEl) {
                        resultEl.textContent = msg;
                        resultEl.classList.add('is-error');
                    }
                }
            );
        });
    });

    // 有効/無効トグル
    document.querySelectorAll('.js-aiap-rss-toggle-enabled').forEach(function (cb) {
        cb.addEventListener('change', function () {
            var card = cb.closest('.aiap-rss-feed-card');
            var feedId = card ? card.getAttribute('data-feed-id') : '';
            if (!feedId) { return; }
            aiapRssPost('aiap_rss_toggle_enabled', {
                feed_id: feedId,
                enabled: cb.checked ? 1 : 0
            }, null, function (msg) {
                cb.checked = !cb.checked; // 失敗時は戻す
                window.alert(msg);
            });
        });
    });

    // タスクカードの有効/無効トグル: change で内包フォームを submit する
    // (既存の hidden input + nonce 経由で admin-post.php の aiap_toggle_task ハンドラを呼ぶ)
    document.querySelectorAll('.js-aiap-task-toggle-cb').forEach(function (cb) {
        cb.addEventListener('change', function () {
            var form = cb.closest('form');
            if (form) { form.submit(); }
        });
    });

    // 履歴クリア
    document.querySelectorAll('.js-aiap-rss-purge-history').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            if (!window.confirm('Purge history for this feed?')) { return; }
            var feedId = btn.getAttribute('data-feed-id');
            aiapRssPost('aiap_rss_purge_history', { feed_id: feedId },
                function (data) {
                    window.alert('Purged ' + (data.purged || 0) + ' guids.');
                    window.location.reload();
                },
                function (msg) {
                    window.alert(msg);
                }
            );
        });
    });

    // 削除確認（admin-post.php への submit は通常フォーム）
    document.querySelectorAll('.js-aiap-rss-delete-confirm').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            if (!window.confirm('Delete this feed and its history?')) {
                ev.preventDefault();
            }
        });
    });

    // 編集モード切替: 「編集」クリックでカードに .is-editing を付与
    document.querySelectorAll('.js-aiap-rss-edit').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            var card = btn.closest('.aiap-rss-feed-card');
            if (!card) { return; }
            card.classList.add('is-editing');
            // 表示名入力にフォーカスを当てて操作しやすく
            var nameInput = card.querySelector('.aiap-rss-feed-edit-form input[name="name"]');
            if (nameInput) { nameInput.focus(); }
        });
    });

    // 編集キャンセル: フォームを初期値に戻して閲覧モードへ
    document.querySelectorAll('.js-aiap-rss-edit-cancel').forEach(function (btn) {
        btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            var card = btn.closest('.aiap-rss-feed-card');
            if (!card) { return; }
            var form = card.querySelector('.aiap-rss-feed-edit-form');
            if (form) { form.reset(); }
            card.classList.remove('is-editing');
        });
    });

    // RSS Use チェックの変化をアコーディオンヘッダの「Configured」バッジに反映する。
    // フォーム未保存でも、チェック ON にした瞬間にヘッダがバッジ付きになる UX 改善 v2.1。
    // 詳細項目自体は常時表示するため、ここでは show/hide は行わない。
    function aiapToggleRssConfiguredBadge(cb) {
        var details = cb.closest('details.aiap-task-section');
        if (!details) { return; }
        var summary = details.querySelector('summary.aiap-task-section__summary');
        if (!summary) { return; }
        var existing = summary.querySelector('.aiap-task-section__configured-badge');
        if (cb.checked) {
            if (!existing) {
                var label = details.getAttribute('data-aiap-configured-label') || 'Configured';
                var badge = document.createElement('span');
                badge.className = 'aiap-task-section__configured-badge';
                badge.textContent = label;
                var title = summary.querySelector('.aiap-task-section__title');
                if (title && title.parentNode) {
                    title.parentNode.insertBefore(badge, title.nextSibling);
                }
            }
        } else if (existing) {
            existing.parentNode.removeChild(existing);
        }
    }
    document.querySelectorAll('.js-aiap-task-rss-enable').forEach(function (cb) {
        cb.addEventListener('change', function () {
            aiapToggleRssConfiguredBadge(cb);
        });
    });
})();
