// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Pre-planning subsections decision wizard.
 *
 * Rendered when the service pauses BEFORE generating the plan because the
 * user's prompt asks for subsections while the per-course toggle is off
 * (`subsections_decision` interrupt). Works like a small wizard inside ONE
 * card that lives INLINE at the end of the chat feed — every step appends
 * chronologically, exactly like reload renders the transcript:
 *
 * - Phase "initial": enable subsections and plan / continue with regular
 *   sections / leave the chat (only when the site cannot enable them).
 * - Phase "structure": choosing "continue" may come back with 2 concrete
 *   flat-structure proposals generated by the AI; the user picks one, writes
 *   their own, or goes BACK to the first question — all inside the same card.
 *
 * Options are radio cards (same look as the proposals card): the first click
 * only selects; confirming — the confirm button or clicking the selected
 * option again — sends ONE user turn + the matching gate action through the
 * feedback WS and resumes the planning stream.
 *
 * @module     local_coursegen/local/courseai/ui/subsections-decision
 * @copyright  2026 Wilber Narvaez <https://datacurso.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

import {sendPlanningFeedback} from 'local_coursegen/repository/course';
import {getDecisionOverlay} from 'local_coursegen/local/courseai/ui/decision-overlay';
import {localizeMessage} from 'local_coursegen/local/courseai/i18n';
import Notification from 'core/notification';

const BLOCK_ID = 'cgSubsectionsDecision';
const RADIO_NAME = 'cgSubsectionsChoice';

/** How many AI structure proposals the card shows at most. */
const MAX_OPTIONS = 2;

/**
 * Last phase-"structure" payload seen this page session. Lets the wizard's
 * back button return to the first question and re-enter the structure step
 * LOCALLY (the server stays paused at its stage-2 interrupt meanwhile).
 */
let lastStructureData = null;

/**
 * Reset module-level state so a new session does not see stale structure data.
 * Call this whenever the UI resets (e.g. backToContext).
 */
export const resetDecisionState = () => {
    lastStructureData = null;
};

/**
 * Remove any previously rendered decision card.
 */
const removeBlock = () => {
    const existing = document.getElementById(BLOCK_ID);
    if (existing) {
        existing.remove();
    }
};

/**
 * Show/hide the right-column loading placeholders. While the decision card is
 * up NOTHING is being generated, so the pulsing skeletons must not suggest
 * otherwise; they come back when the user answers and planning resumes.
 *
 * @param {boolean} visible
 */
const toggleCenterLoading = (visible) => {
    ['planningLoading', 'cgCenterSkeleton', 'cgLeftSkeleton'].forEach((id) => {
        const el = document.getElementById(id);
        if (el) {
            el.style.display = visible ? '' : 'none';
        }
    });
};

/**
 * Show/hide the generic Accept/Adjust row + title of the decision overlay.
 * The subsections card brings its own options, and Accept/Adjust make no
 * sense before a plan exists (same pattern as the proposals card).
 *
 * @param {boolean} visible
 */
const toggleDecisionActions = (visible) => {
    ['.cg-decision-actions', '.cg-decision-subtitle', '.cg-decision-title'].forEach((selector) => {
        const el = document.querySelector('.cg-decision-overlay ' + selector);
        if (el) {
            el.style.display = visible ? '' : 'none';
        }
    });
};

/**
 * Place the card in the bottom decision slot (the composer's place), like the
 * review card. Fallback: end of the chat feed, in the SAME container emitLog
 * targets so the turns around it stay chronological.
 *
 * @param {HTMLElement} block
 * @param {Object} texts
 * @param {Object} state - Shared courseai state (read for planEverReviewed).
 */
const attachBlock = (block, texts, state) => {
    const overlay = getDecisionOverlay(texts);
    const body = overlay.getBody();
    if (body) {
        body.appendChild(block);
        overlay.show();
        return;
    }
    const log = document.getElementById('cgLog');
    const logAfter = document.getElementById('cgLogAfter');
    const feed = (state && state.planEverReviewed && logAfter) ? logAfter : (log || logAfter);
    if (!feed) {
        return;
    }
    feed.appendChild(block);
    window.requestAnimationFrame(() => {
        block.scrollIntoView({block: 'nearest', inline: 'nearest'});
    });
};

/**
 * The service flips with_subsections in the session config; mirror it in the
 * client so the "+" menu toggle reads ON. Dispatching change on the hidden
 * checkbox reuses the existing listeners (state + both toolbars +
 * aria-checked); the direct state write is the fallback when the toggle is
 * not rendered.
 *
 * @param {Object} state
 */
const syncClientToggleOn = (state) => {
    state.withSubsections = true;
    const checkbox = document.getElementById('btnWithSubsections')
        || document.getElementById('btnCompactWithSubsections');
    if (checkbox && !checkbox.checked) {
        checkbox.checked = true;
        checkbox.dispatchEvent(new Event('change', {bubbles: true}));
    }
};

/**
 * Render the subsections decision wizard and wire its options.
 *
 * @param {Object} args
 * @param {Object} args.data - Interrupt payload: {phase, available, message,
 *     options?, allow_custom?}.
 * @param {Object} args.ctx  - Stream handler context (or a resume-built
 *     equivalent): state, texts, emitLog, stepsUi, openSSEStream.
 * @returns {Promise<void>}
 */
export const renderSubsectionsDecision = async({data, ctx}) => {
    if (data.phase === 'structure') {
        lastStructureData = data;
    }
    await renderStep({data, ctx, cameBack: false});
};

/**
 * Render ONE wizard step (a phase of the decision) into a fresh card.
 *
 * @param {Object} args
 * @param {Object} args.data     - Payload of the phase being rendered.
 * @param {Object} args.ctx      - Stream handler context.
 * @param {boolean} args.cameBack - True when this step was reached through
 *     the wizard's back button (the server did NOT re-ask: navigation is
 *     purely client-side and "continue" must re-enter the structure step
 *     locally instead of re-sending the action).
 */
const renderStep = async({data, ctx, cameBack}) => {
    const {state, texts, emitLog, stepsUi, openSSEStream} = ctx;

    removeBlock();

    // Nothing is being generated while the user decides: stop the pulsing
    // placeholders and the generic overlay actions (Accept/Adjust have no
    // plan to act on yet).
    toggleCenterLoading(false);
    toggleDecisionActions(false);

    const question = await localizeMessage(data.message);
    const isStructure = data.phase === 'structure';

    const block = document.createElement('div');
    block.id = BLOCK_ID;
    block.className = 'cg-feed-proposals cg-subsections-decision';

    const title = document.createElement('p');
    title.className = 'plan-proposals-title';
    title.textContent = question || texts.courseai_subsections_decision_title || '';
    block.appendChild(title);

    const group = document.createElement('div');
    group.className = 'plan-proposals-group';

    // ── Wizard chrome: radio selection + footer (back / confirm) ──────────
    let selected = null;

    const footer = document.createElement('div');
    footer.className = 'cg-subsections-footer';

    const confirmBtn = document.createElement('button');
    confirmBtn.type = 'button';
    confirmBtn.className = 'btn-proposals-apply cg-subsections-confirm';
    confirmBtn.textContent = texts.courseai_subsections_confirm || 'Confirm';
    confirmBtn.disabled = true;

    const confirmSelected = () => {
        if (!selected) {
            return;
        }
        if (selected.custom) {
            const value = (selected.textarea.value || '').trim();
            if (!value) {
                selected.textarea.focus();
                return;
            }
            selected.run(value);
            return;
        }
        selected.run();
    };

    confirmBtn.addEventListener('click', confirmSelected);

    /**
     * Add one selectable radio option (same DOM as the proposals card).
     * Checking it selects; clicking the ALREADY-selected option (or the
     * confirm button) confirms.
     *
     * @param {string} label
     * @param {Function} run   - Executed on confirm.
     * @param {Object} [extra] - {custom, textarea, confirmLabel}.
     */
    const addOption = (label, run, extra) => {
        const card = document.createElement('label');
        card.className = 'plan-proposal-card cg-subsections-option';

        const radio = document.createElement('input');
        radio.type = 'radio';
        radio.name = RADIO_NAME;
        radio.className = 'plan-proposal-radio';

        const text = document.createElement('span');
        text.className = 'plan-proposal-summary';
        text.textContent = label;

        card.appendChild(radio);
        card.appendChild(text);

        const option = {run, radio, ...(extra || {})};

        radio.addEventListener('change', () => {
            if (!radio.checked) {
                return;
            }
            selected = option;
            confirmBtn.textContent = option.confirmLabel
                || texts.courseai_subsections_confirm || 'Confirm';
            confirmBtn.disabled = false;
            if (option.textarea) {
                option.textarea.style.display = '';
                option.textarea.focus();
            }
            group.querySelectorAll('textarea').forEach((area) => {
                if (!option.textarea || area !== option.textarea) {
                    area.style.display = 'none';
                }
            });
        });

        // Second click on the selected option = confirm (radios fire no
        // second change event, so this rides the plain click).
        card.addEventListener('click', () => {
            if (selected === option && radio.checked && !option.custom) {
                confirmSelected();
            }
        });

        group.appendChild(card);
    };

    // Confirmed choices leave the wizard: log ONE user turn, close the card
    // and resume the planning stream with the gate action.
    const finish = async(logMessage, pendingAction) => {
        if (typeof emitLog === 'function' && logMessage) {
            emitLog({actor: 'user', kind: 'user', message: logMessage});
        }
        removeBlock();
        toggleDecisionActions(true);
        getDecisionOverlay().hide();
        if (pendingAction) {
            // Planning resumes: bring the loading placeholders back until the
            // first section event replaces them with real content.
            toggleCenterLoading(true);
            try {
                await sendPlanningFeedback({recordid: state.sessionid, pendingAction});
                // Resume planning in NORMAL mode (no keepPlan: nothing is
                // rendered yet — the initial plan must stream fresh).
                openSSEStream(state.streamingurl, 0, 'planning');
            } catch (error) {
                toggleCenterLoading(false);
                await Notification.exception(error);
            }
        }
    };

    // ── Step "structure": pick (or write) a flat structure ────────────────
    if (isStructure) {
        (data.options || []).slice(0, MAX_OPTIONS).forEach((optionText) => {
            const text = String(optionText || '').trim();
            if (!text) {
                return;
            }
            addOption(text, () => finish(text, {action: 'apply_structure_choice', instruction: text}));
        });

        // Free-text alternative: "describe your own structure" (same pattern
        // as the proposals card's "Something else": textarea shows on check).
        const customArea = document.createElement('textarea');
        customArea.className = 'plan-proposal-other-textarea';
        customArea.rows = 3;
        customArea.placeholder = texts.courseai_subsections_structure_other
            || 'Another structure: describe it…';
        customArea.style.display = 'none';
        addOption(
            texts.courseai_subsections_structure_other || 'Another structure: describe it…',
            (value) => finish(value, {action: 'apply_structure_choice', instruction: value}),
            {
                custom: true,
                textarea: customArea,
                confirmLabel: texts.courseai_subsections_structure_send || 'Use this structure',
            }
        );
        group.appendChild(customArea);

        // Back to the first question — client-side only (the server keeps
        // waiting at its stage-2 pause; enable_subsections is accepted from
        // there too, so every phase-1 choice still works after going back).
        const backBtn = document.createElement('button');
        backBtn.type = 'button';
        backBtn.className = 'cg-subsections-back';
        backBtn.textContent = texts.courseai_subsections_back || 'Back to the previous question';
        backBtn.addEventListener('click', () => {
            renderStep({
                data: {
                    type: 'subsections_decision',
                    phase: 'initial',
                    available: data.available,
                    message: {
                        string_id: data.available
                            ? 'subsections_decision_available'
                            : 'subsections_decision_unavailable',
                    },
                },
                ctx,
                cameBack: true,
            });
        });
        footer.appendChild(backBtn);
    } else {
        // ── Step "initial": enable / continue flat / leave ────────────────
        const continueFlat = () => {
            if (cameBack && lastStructureData) {
                // The structure question was already generated and the server
                // is still paused on it: just re-enter that step locally.
                renderStep({data: lastStructureData, ctx, cameBack: false});
                return;
            }
            finish(
                texts.courseai_subsections_decision_continue || 'Continue with regular sections',
                {action: 'continue_without_subsections'}
            );
        };

        if (data.available) {
            addOption(
                texts.courseai_subsections_decision_enable || 'Enable subsections and plan',
                () => {
                    syncClientToggleOn(state);
                    finish(
                        texts.courseai_subsections_decision_enable || 'Enable subsections and plan',
                        {action: 'enable_subsections'}
                    );
                }
            );
            addOption(
                texts.courseai_subsections_decision_continue || 'Continue with regular sections',
                continueFlat
            );
        } else {
            addOption(
                texts.courseai_subsections_decision_continue || 'Continue with regular sections',
                continueFlat
            );
            addOption(
                texts.courseai_subsections_decision_exit || 'Leave the chat',
                () => {
                    if (typeof emitLog === 'function') {
                        emitLog({
                            actor: 'user', kind: 'user',
                            message: texts.courseai_subsections_decision_exit || 'Leave the chat',
                        });
                    }
                    removeBlock();
                    toggleDecisionActions(true);
                    getDecisionOverlay().hide();
                    // Purely client-side exit: back to the context form. The
                    // abandoned session stays interrupted server-side, like
                    // any other abandoned planning session.
                    if (stepsUi && typeof stepsUi.backToContext === 'function') {
                        stepsUi.backToContext();
                    }
                }
            );
        }
    }

    block.appendChild(group);
    footer.appendChild(confirmBtn);
    block.appendChild(footer);
    attachBlock(block, texts, state);
};
