import blessed from 'blessed';
import contrib from 'blessed-contrib';
import clipboard from 'clipboardy';

/**
 * Browser TUI interactif pour la documentation API Rainbow.
 * Layout en panneaux : arbre (gauche 40%) + detail (droite 60%).
 */
export class ApiDocsBrowser {
    constructor({ screen, treeData, getEndpointDetails, executeEndpoint, defaults = {},
                  portal = null, getVersions = null, switchVersion = null, connectedUser = null }) {
        this.screen = screen;
        this.getEndpointDetails = getEndpointDetails;
        this.executeEndpoint = executeEndpoint;
        this.defaults = defaults; // valeurs par defaut pour les champs (companyId, userId...)
        this.portal = portal;             // portail unique affiché (null = tous)
        this.getVersions = getVersions;   // callback () => Promise<[{version,label,isCurrent}]>
        this.switchVersion = switchVersion; // callback (portal, version) => Promise<treeData>
        this.currentPortalVersion = null; // label de la version active (mise à jour lors du switch)
        this.connectedUser = connectedUser; // nom/email de l'utilisateur connecté
        this.searchKeyword = '';
        this.lastSearchIndex = -1;
        this.flatNodeList = [];
        this.focusedPanel = 'tree';
        this.currentEndpoint = null; // endpoint actuellement affiche dans le detail panel
        this.tryOutOverlay = null;   // overlay formulaire "Try it out"
        this._responseBox = null;    // box plein-ecran affichant la réponse HTTP

        // Map des noeuds endpoint pour la navigation rapide
        this._nodeMap = new Map();

        this._createWidgets();
        this.treeData = treeData;
        this.tree.setData(treeData);
        this.flatNodeList = this._flattenTree(treeData);
        this._buildNodeMap(treeData);
        this._initEventListeners();
        this.tree.focus();
        this._updateFocusStyle();
        this._updateStatusBar();
    }

    _createWidgets() {
        // Barre de statut (haut)
        this.statusBar = blessed.box({
            parent: this.screen,
            top: 0,
            left: 0,
            width: '100%',
            height: 3,
            tags: true,
            border: { type: 'line' },
            style: { fg: 'white', bg: 'blue', border: { fg: 'cyan' } },
            content: '' // mis à jour par _updateStatusBar()
        });

        // Panneau arbre (gauche 40%)
        this.tree = contrib.tree({
            parent: this.screen,
            top: 3,
            left: 0,
            width: '40%',
            height: '100%-6',
            border: { type: 'line' },
            label: ' {bold}Endpoints{/bold} ',
            tags: true,
            vi: true,
            mouse: true,
            style: {
                fg: 'white',
                border: { fg: 'green' },
                selected: { bg: 'blue' }
            },
            scrollbar: {
                ch: ' ',
                track: { bg: 'yellow' },
                style: { inverse: true }
            }
        });

        // Panneau detail (droite 60%)
        this.detailPanel = blessed.box({
            parent: this.screen,
            top: 3,
            left: '40%',
            width: '60%',
            height: '100%-6',
            tags: true,
            border: { type: 'line' },
            label: ' Details ',
            scrollable: true,
            alwaysScroll: true,
            keys: true,
            vi: true,
            mouse: true,
            style: {
                fg: 'white',
                border: { fg: 'cyan' }
            },
            scrollbar: {
                ch: ' ',
                track: { bg: 'yellow' },
                style: { inverse: true }
            },
            content: '\n  Select an endpoint from the tree to view details.\n\n  Use arrow keys to navigate, Enter to expand/select.\n  Press ? or F1 for full help.'
        });

        // Barre de log / statut (bas)
        this.logBar = blessed.box({
            parent: this.screen,
            bottom: 0,
            left: 0,
            width: '100%',
            height: 3,
            tags: true,
            border: { type: 'line' },
            style: { fg: 'white', border: { fg: 'cyan' } },
            content: '' // mis à jour par _updateStatusBar()
        });

        // Champ de recherche (masque)
        this.searchInput = blessed.textbox({
            parent: this.screen,
            bottom: 0,
            left: 0,
            width: '100%',
            height: 3,
            inputOnFocus: true,
            hidden: true,
            border: { type: 'line' },
            style: { fg: 'white', bg: 'blue', border: { fg: 'white' } },
            label: ' Search (Enter to validate, Escape to cancel) '
        });

        // Overlay d'aide (masque)
        this.helpBox = blessed.box({
            parent: this.screen,
            top: 'center',
            left: 'center',
            width: '60%',
            height: '60%',
            tags: true,
            border: { type: 'line' },
            style: { fg: 'white', bg: 'black', border: { fg: 'white' } },
            label: ' Help (Escape to hide) ',
            hidden: true,
            content: `
  {bold}Rainbow API Documentation Browser{/bold}

  {bold}Navigation{/bold}
  Up / Down         Navigate the endpoint tree
  Enter / Space     Expand / collapse a branch or select endpoint
  Tab               Switch focus between tree and detail panel
  PgUp / PgDown     Scroll the detail panel

  {bold}Search{/bold}
  /                 Open search
  Enter             Submit search
  n                 Next search result
  Escape            Cancel search

  {bold}Try it out{/bold}
  x                 Open "Try it out" form for the selected endpoint
  Ctrl+V            Paste clipboard into the focused field
  Ctrl+P            Refresh the request preview (URL + body)
  Ctrl+E            Execute the request (inside the form)
  Escape            Cancel form / close response panel

  {bold}Versions{/bold}
  v                 Switch portal version (mode portail unique uniquement)

  {bold}General{/bold}
  F1 or ?           Show / hide this help
  Escape / q        Quit application`
        });
    }

    _initEventListeners() {
        // Selection d'un endpoint dans l'arbre (Enter ou clic)
        this.tree.on('select', (node) => {
            if (node.portal && node.method && node.endpointPath) {
                this._showEndpointDetails(node.portal, node.method, node.endpointPath);
            }
        });

        // Navigation dans l'arbre : affichage auto des details
        this.tree.rows.on('keypress', (_ch, key) => {
            if (key.name === 'up' || key.name === 'down') {
                process.nextTick(() => this._onTreeMove());
            }
        });

        // Tab : changer le focus entre les panneaux (désactivé quand l'overlay est ouvert —
        // le Tab est alors géré au niveau widget via widget.key('tab') + readInput manuel)
        this.screen.key('tab', () => {
            if (this.tryOutOverlay) return;
            if (this.focusedPanel === 'tree') {
                this.focusedPanel = 'detail';
                this.detailPanel.focus();
            } else {
                this.focusedPanel = 'tree';
                this.tree.focus();
            }
            this._updateFocusStyle();
            this.screen.render();
        });

        // Recherche
        this.screen.key('/', () => {
            if (!this.helpBox.hidden) return;
            this.searchInput.show();
            this.searchInput.focus();
            this.screen.render();
        });

        this.searchInput.on('submit', (value) => {
            this.searchKeyword = value;
            this.lastSearchIndex = -1;
            this._findNext();
            this.searchInput.hide();
            this.tree.focus();
            this.focusedPanel = 'tree';
            this._updateFocusStyle();
            this.screen.render();
        });

        this.searchInput.on('cancel', () => {
            this.searchInput.hide();
            this.tree.focus();
            this.focusedPanel = 'tree';
            this._updateFocusStyle();
            this.screen.render();
        });

        this.screen.key('n', () => {
            if (this.searchKeyword && this.searchInput.hidden) {
                this._findNext();
            }
        });

        // Aide
        this.screen.key(['f1', '?'], () => {
            if (this.helpBox.hidden) {
                this.helpBox.show();
                this.helpBox.focus();
            } else {
                this.helpBox.hide();
                if (this.focusedPanel === 'tree') this.tree.focus();
                else this.detailPanel.focus();
            }
            this.screen.render();
        });

        // Try it out
        this.screen.key('x', () => {
            if (!this.helpBox.hidden || !this.searchInput.hidden || this.tryOutOverlay) return;
            this._startTryOut();
        });

        // Sélection de version (mode portail unique uniquement)
        this.screen.key('v', () => {
            if (!this.getVersions || !this.switchVersion) return;
            if (!this.helpBox.hidden || !this.searchInput.hidden || this.tryOutOverlay) return;
            this._openVersionSelector();
        });

        // Execute (Ctrl+E) — actif seulement si overlay ouvert
        this.screen.key('C-e', () => {
            if (this.tryOutOverlay) {
                this.tryOutOverlay._doExecute();
            }
        });

        // Preview (Ctrl+P) — Ctrl évite l'insertion du caractère dans un textbox actif
        this.screen.key('C-p', () => {
            if (this.tryOutOverlay) {
                this.tryOutOverlay._refreshPreview();
            }
        });

        // Quitter
        this.screen.key(['escape', 'q'], () => {
            // Priorite 1 : fermer l'overlay try-out.
            // Note : quand un textbox est en readInput (grabKeys=true), ce handler est
            // bloqué ; c'est le widget.key('escape') dans _buildTryOutOverlay qui prend la main.
            if (this.tryOutOverlay) {
                this._closeOverlay();
                return;
            }
            // Priorite 2 : fermer le panneau de réponse (screen.key() est dispatché AVANT
            // responseBox.key(), donc on doit intercepter ici avant process.exit).
            if (this._responseBox) {
                this._closeResponseBox();
                return;
            }
            if (!this.helpBox.hidden) {
                this.helpBox.hide();
                if (this.focusedPanel === 'tree') this.tree.focus();
                else this.detailPanel.focus();
                this.screen.render();
            } else if (!this.searchInput.hidden) {
                this.searchInput.hide();
                this.tree.focus();
                this.focusedPanel = 'tree';
                this._updateFocusStyle();
                this.screen.render();
            } else {
                this._confirmQuit();
            }
        });
    }

    _confirmQuit() {
        const box = blessed.box({
            parent:  this.screen,
            top:     'center',
            left:    'center',
            width:   36,
            height:  5,
            tags:    true,
            border:  { type: 'line' },
            style:   { fg: 'white', bg: 'black', border: { fg: 'yellow' } },
            label:   ' Quitter ',
            content: '\n  Quitter le browser ?  {bold}o{/bold} / {bold}n{/bold}',
        });

        const close = () => { box.destroy(); this.screen.render(); };

        box.key(['o', 'O', 'y', 'Y', 'enter'], () => { close(); process.exit(0); });
        box.key(['n', 'N', 'escape', 'q'],       () => { close(); });

        box.focus();
        this.screen.render();
    }


    _updateStatusBar(versionLabel = null) {
        const activeVersion = versionLabel || this.currentPortalVersion || '';
        let title = ' {bold}Rainbow API Documentation Browser{/bold}';
        if (this.portal) {
            title += `  |  {cyan-fg}${this.portal}{/cyan-fg}`;
            if (activeVersion) title += `  ${activeVersion}`;
        }
        if (this.connectedUser) {
            const u = this.connectedUser;
            if (u.host) title += `  |  {green-fg}${u.host}{/green-fg}`;
            let userInfo = `{yellow-fg}${u.name}{/yellow-fg}`;
            if (u.companyName) userInfo += ` {white-fg}(${u.companyName}){/white-fg}`;
            if (u.roles?.length > 0) userInfo += `  {cyan-fg}[${u.roles.join(', ')}]{/cyan-fg}`;
            title += `  |  ${userInfo}`;
        }
        this.statusBar.setContent(title);

        const vHint = this.getVersions ? '  {bold}v{/bold} Versions' : '';
        this.logBar.setContent(` {bold}?{/bold} Help  {bold}/{/bold} Search  {bold}Tab{/bold} Switch panel  {bold}x{/bold} Try it${vHint}  {bold}q{/bold} Quit`);
    }

    async _openVersionSelector() {
        let versions;
        try {
            versions = await this.getVersions();
        } catch (err) {
            this.logBar.setContent(` {red-fg}Failed to load versions: ${err.message}{/red-fg}`);
            this.screen.render();
            return;
        }

        if (versions.length <= 1) {
            this.logBar.setContent(' {yellow-fg}No archived versions available{/yellow-fg}');
            this.screen.render();
            return;
        }

        const listOverlay = blessed.list({
            parent: this.screen,
            top: 'center',
            left: 'center',
            width: '50%',
            height: Math.min(versions.length + 4, 20),
            tags: true,
            border: { type: 'line' },
            label: ' Select version (Enter: select, Esc: cancel) ',
            keys: true,
            vi: true,
            mouse: true,
            style: {
                fg: 'white',
                bg: 'black',
                border: { fg: 'yellow' },
                selected: { bg: 'blue' }
            },
            items: versions.map(v => v.isCurrent ? `{green-fg}${v.label}{/green-fg}` : v.label)
        });

        let handled = false;

        const closeList = () => {
            if (handled) return;
            handled = true;
            listOverlay.destroy();
            this.tree.focus();
            this.focusedPanel = 'tree';
            this._updateFocusStyle();
            this.screen.render();
        };

        listOverlay.key(['escape', 'q'], () => closeList());

        listOverlay.on('select', async (_item, index) => {
            if (handled) return;
            handled = true;
            listOverlay.destroy();

            const selected = versions[index];
            this.logBar.setContent(` {yellow-fg}Loading ${selected.label}...{/yellow-fg}`);
            this.screen.render();

            try {
                const newTreeData = await this.switchVersion(this.portal, selected.version);

                this.treeData = newTreeData;
                this.tree.setData(newTreeData);
                this.flatNodeList = this._flattenTree(newTreeData);
                this._nodeMap = new Map();
                this._buildNodeMap(newTreeData);

                // Vider le panneau detail (l'endpoint sélectionné peut ne plus exister)
                this.currentEndpoint = null;
                this.detailPanel.setContent('\n  Select an endpoint from the tree to view details.');

                this.currentPortalVersion = selected.label;
                this._updateStatusBar(selected.label);
                this.logBar.setContent(` {green-fg}Switched to ${selected.label}{/green-fg}`);
            } catch (err) {
                this._updateStatusBar();
                this.logBar.setContent(` {red-fg}Failed to switch version: ${err.message}{/red-fg}`);
            }

            this.tree.focus();
            this.focusedPanel = 'tree';
            this._updateFocusStyle();
            this.screen.render();
        });

        listOverlay.focus();
        this.screen.render();
    }

    _onTreeMove() {
        const selected = this.tree.rows.items[this.tree.rows.selected];
        if (!selected) return;
        const content = selected.content.trim();
        const node = this._nodeMap.get(content);
        if (node) {
            this._showEndpointDetails(node.portal, node.method, node.endpointPath);
        }
    }

    _updateFocusStyle() {
        if (this.focusedPanel === 'tree') {
            this.tree.style.border.fg = 'green';
            this.detailPanel.style.border.fg = 'cyan';
        } else {
            this.tree.style.border.fg = 'cyan';
            this.detailPanel.style.border.fg = 'green';
        }
    }

    _showEndpointDetails(portal, method, endpointPath) {
        const details = this.getEndpointDetails(portal, method, endpointPath);
        if (!details || details.error) {
            this.detailPanel.setContent(` {red-fg}${details?.error || 'Failed to load endpoint details'}{/red-fg}`);
            this.currentEndpoint = null;
            this.screen.render();
            return;
        }
        this.currentEndpoint = { portal, method, endpointPath };

        const methodColors = { GET: 'green', POST: 'yellow', PUT: 'blue', DELETE: 'red', PATCH: 'magenta' };
        const color = methodColors[details.method] || 'white';
        const esc = (s) => this._escapeBraces(s);

        let c = '\n';
        c += ` {${color}-fg}{bold}${details.method}{/bold}{/${color}-fg} ${esc(details.path)}\n`;
        c += ` {cyan-fg}Portal:{/cyan-fg} ${details.portal} (${esc(details.portalTitle || '')})\n`;

        if (details.summary) {
            c += `\n {bold}Summary{/bold}\n`;
            c += ` ${esc(details.summary)}\n`;
        }

        if (details.tags?.length > 0) {
            c += `\n {cyan-fg}Tags:{/cyan-fg} ${details.tags.join(', ')}\n`;
        }
        if (details.permissions?.length > 0) {
            c += ` {cyan-fg}Permissions:{/cyan-fg} ${details.permissions.join(', ')}\n`;
        }
        if (details.operationId) {
            c += ` {cyan-fg}Operation ID:{/cyan-fg} ${details.operationId}\n`;
        }

        if (details.description) {
            c += `\n {bold}Description{/bold}\n`;
            for (const line of details.description.split('\n')) {
                c += ` ${esc(line)}\n`;
            }
        }

        if (details.parameters?.length > 0) {
            c += `\n {bold}Parameters{/bold}\n`;
            for (const p of details.parameters) {
                const req = p.required ? '{red-fg}*{/red-fg}' : ' ';
                const enumStr = p.enum ? ` [${p.enum.join('|')}]` : '';
                c += ` ${req} {cyan-fg}${p.name}{/cyan-fg} (${p.in}, ${p.type})${enumStr}\n`;
                if (p.description) {
                    c += `     ${esc(p.description)}\n`;
                }
            }
        }

        if (details.requestBody) {
            const reqLabel = details.requestBody.required ? '{red-fg}(required){/red-fg}' : '(optional)';
            c += `\n {bold}Request Body{/bold} ${reqLabel}\n`;
            c += this._formatSchema(details.requestBody.schema, 1);
        }

        if (details.responses) {
            c += `\n {bold}Responses{/bold}\n`;
            for (const [code, resp] of Object.entries(details.responses)) {
                const cc = code.startsWith('2') ? 'green' : code.startsWith('4') ? 'yellow' : code.startsWith('5') ? 'red' : 'white';
                c += ` {${cc}-fg}{bold}${code}{/bold}{/${cc}-fg} ${esc(resp.description || '')}\n`;
                if (resp.schema?.properties) {
                    c += this._formatSchema(resp.schema, 1, true);
                }
            }
        }

        this.detailPanel.setContent(c);
        this.detailPanel.scrollTo(0);
        const vHint = this.getVersions ? '  {bold}v{/bold} Versions' : '';
        this.logBar.setContent(` {bold}${details.method}{/bold} ${esc(details.path)} | {bold}Tab{/bold} scroll  {bold}x{/bold} Try it out${vHint}`);
        this.screen.render();
    }

    // -----------------------------------------------------------------------
    // Try it out
    // -----------------------------------------------------------------------

    _closeOverlay() {
        if (!this.tryOutOverlay) return;
        this.tryOutOverlay.destroy();
        this.tryOutOverlay = null;
        if (this._tryOutBlocker) { this._tryOutBlocker.destroy(); this._tryOutBlocker = null; }
        this.tree.focus();
        this.focusedPanel = 'tree';
        this._updateFocusStyle();
        this.screen.render();
    }

    _closeResponseBox() {
        if (!this._responseBox) return;
        const box = this._responseBox;
        this._responseBox = null;
        box.destroy();
        this.tree.focus();
        this.focusedPanel = 'tree';
        this._updateFocusStyle();
        // Ré-afficher la doc de l'endpoint là où on était avant l'exécution
        if (this.currentEndpoint) {
            const { portal, method, endpointPath } = this.currentEndpoint;
            this._showEndpointDetails(portal, method, endpointPath);
        }
        this.screen.render();
    }

    _startTryOut() {
        if (!this.currentEndpoint) {
            this.logBar.setContent(' {yellow-fg}Select an endpoint first{/yellow-fg}');
            this.screen.render();
            return;
        }
        if (!this.executeEndpoint) {
            this.logBar.setContent(' {yellow-fg}executeEndpoint not configured{/yellow-fg}');
            this.screen.render();
            return;
        }

        const { portal, method, endpointPath } = this.currentEndpoint;
        const details = this.getEndpointDetails(portal, method, endpointPath);
        if (!details || details.error) return;

        const pathParams  = (details.parameters || []).filter(p => p.in === 'path');
        const queryParams = (details.parameters || []).filter(p => p.in === 'query');
        const hasBody     = !!details.requestBody && ['POST', 'PUT', 'PATCH'].includes(details.method);

        this._buildTryOutOverlay(details, pathParams, queryParams, hasBody);
    }

    _buildTryOutOverlay(details, pathParams, queryParams, hasBody) {
        const methodColors = { GET: 'green', POST: 'yellow', PUT: 'blue', DELETE: 'red', PATCH: 'magenta' };
        const color = methodColors[details.method] || 'white';
        const esc = (s) => this._escapeBraces(s || '');

        // Backdrop plein-ecran qui intercepte les events souris avant qu'ils
        // n'atteignent le tree/detail situes en dessous de l'overlay.
        this._tryOutBlocker = blessed.box({
            parent: this.screen,
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            mouse: true,
            clickable: true,
            style: { transparent: true }
        });

        const overlay = blessed.box({
            parent: this.screen,
            top: 3,
            left: '5%',
            width: '90%',
            height: '100%-6',
            mouse: true,
            clickable: true,
            tags: true,
            border: { type: 'line' },
            label: ' Try it out — {bold}Ctrl+E{/bold}: execute  {bold}Ctrl+P{/bold}: preview  {bold}Esc{/bold}: cancel ',
            style: { fg: 'white', bg: 'black', border: { fg: 'yellow' } }
        });

        // En-tête
        blessed.text({
            parent: overlay,
            top: 0,
            left: 1,
            tags: true,
            content: `{${color}-fg}{bold}${details.method}{/bold}{/${color}-fg} ${esc(details.path)}`
        });

        // Construction des champs
        const allFields = []; // { param, widget }
        let yOffset = 2;

        const addSectionLabel = (label) => {
            blessed.text({ parent: overlay, top: yOffset, left: 1, tags: true, content: `{cyan-fg}{bold}${label}{/bold}{/cyan-fg}` });
            yOffset += 1;
        };

        const addTextField = (param) => {
            const reqLabel = param.required ? ' {red-fg}*{/red-fg}' : '';
            const defaultVal = this.defaults[param.name] || '';
            const defaultHint = defaultVal ? ` — default: ${defaultVal}` : '';
            const tb = blessed.textbox({
                parent: overlay,
                top: yOffset,
                left: 2,
                width: '100%-6',
                height: 3,
                // NE PAS utiliser inputOnFocus: readInput() est géré manuellement pour
                // éviter le rewindFocus() et les listeners zombies (bug blessed nextTick).
                border: { type: 'line' },
                label: ` ${param.name} (${param.type || 'string'})${reqLabel}${defaultHint} `,
                tags: true,
                value: defaultVal,
                style: {
                    fg: 'white',
                    border: { fg: defaultVal ? 'green' : 'cyan' },
                    focus: { border: { fg: 'yellow' } }
                }
            });
            allFields.push({ param, widget: tb });
            yOffset += 3;
        };

        if (pathParams.length > 0) {
            addSectionLabel('Path Parameters');
            for (const p of pathParams) addTextField(p);
            yOffset += 1;
        }

        if (queryParams.length > 0) {
            addSectionLabel('Query Parameters  (leave empty to omit)');
            for (const p of queryParams) addTextField(p);
            yOffset += 1;
        }

        // Textarea JSON pour le body
        let bodyWidget = null;
        if (hasBody) {
            addSectionLabel('Request Body (JSON)');
            const template = this._buildBodyTemplate(details.requestBody);
            bodyWidget = blessed.textarea({
                parent: overlay,
                top: yOffset,
                left: 2,
                width: '100%-6',
                height: 8,
                border: { type: 'line' },
                label: ' body (JSON) ',
                tags: false,
                value: template,
                style: {
                    fg: 'white',
                    border: { fg: 'cyan' },
                    focus: { border: { fg: 'yellow' } }
                }
            });
        }

        // Navigation Tab circulaire entre tous les widgets
        const allWidgets = allFields.map(f => f.widget);
        if (bodyWidget) allWidgets.push(bodyWidget);

        overlay._focusIndex = 0;

        // Helper : démarre readInput seulement si pas déjà actif (évite les sessions dupliquées)
        const safeReadInput = (w) => {
            if (!w._reading) w.readInput(() => {});
        };

        // Helper : donne le focus au widget i et relance readInput
        const focusAt = (idx) => {
            overlay._focusIndex = ((idx % allWidgets.length) + allWidgets.length) % allWidgets.length;
            const next = allWidgets[overlay._focusIndex];
            next.focus();
            process.nextTick(() => {
                safeReadInput(next);
                overlay._refreshPreview?.();
                this.screen.render();
            });
        };

        // Tab par widget (actif même avec grabKeys=true)
        for (let i = 0; i < allWidgets.length; i++) {
            const curWidget = allWidgets[i];
            curWidget.key('tab', () => {
                // Strip le \t inséré par blessed avant de changer de widget
                const v = curWidget.getValue();
                const stripped = v.replace(/\t/g, '');
                if (v !== stripped) curWidget.setValue(stripped);
                focusAt(i + 1);
            });
        }

        // Récupération si le focus revient sur un widget via la souris
        for (let i = 0; i < allWidgets.length; i++) {
            allWidgets[i].on('focus', () => {
                if (overlay._focusIndex !== i) overlay._focusIndex = i;
                process.nextTick(() => safeReadInput(allWidgets[i]));
            });
        }

        // Récupération si le focus atterrit sur le fond de l'overlay (clic sur bordure/fond)
        overlay.on('focus', () => {
            process.nextTick(() => focusAt(overlay._focusIndex));
        });

        // Raccourcis clavier au niveau widget (actifs même avec grabKeys=true, contrairement à screen.key)
        for (const w of allWidgets) {
            w.key('C-e', () => { overlay._doExecute?.(); });
            w.key('C-p', () => { overlay._refreshPreview?.(); });
            w.key('escape', () => { this._closeOverlay(); });
            w.key('C-v', () => {
                try {
                    const text = clipboard.readSync();
                    if (text) {
                        w.setValue(w.getValue() + text);
                        if (overlay._refreshPreview) overlay._refreshPreview();
                        this.screen.render();
                    }
                } catch (_e) { /* clipboard indisponible */ }
            });
        }

        // Preview box (bottom of overlay, tags:false pour éviter l'interférence avec les accolades JSON)
        const previewBox = blessed.box({
            parent: overlay,
            bottom: 0,
            left: 1,
            width: '100%-4',
            height: 8,
            tags: false,
            border: { type: 'line' },
            label: ' Request Preview (Ctrl+P: refresh) ',
            scrollable: true,
            style: {
                fg: 'cyan',
                border: { fg: 'blue' }
            }
        });

        // Stocker les références sur l'overlay pour accès depuis _buildRequestPreview
        overlay._previewBox = previewBox;
        overlay._fields = allFields;
        overlay._bodyWidget = bodyWidget;

        // Méthode de rafraîchissement du preview
        overlay._refreshPreview = () => {
            previewBox.setContent(this._buildRequestPreview());
            this.screen.render();
        };

        // Méthode d'exécution attachée à l'overlay (appelée par C-e)
        overlay._doExecute = () => {
            this._executeTryOut(allFields, bodyWidget);
        };

        this.tryOutOverlay = overlay;

        // Preview initial
        overlay._refreshPreview();

        if (allWidgets.length > 0) {
            allWidgets[0].focus();
            safeReadInput(allWidgets[0]);
        }
        this.screen.render();
    }

    _buildBodyTemplate(requestBody) {
        const schema = requestBody?.schema;
        if (!schema?.properties) return '{}';
        return JSON.stringify(this._schemaToTemplate(schema), null, 2);
    }

    _schemaToTemplate(schema) {
        if (!schema?.properties) return null;
        const required = new Set(schema.required || []);
        const template = {};
        for (const [name, prop] of Object.entries(schema.properties)) {
            if (required.has(name)) {
                if (prop.type === 'string')             template[name] = '';
                else if (prop.type === 'number')        template[name] = 0;
                else if (prop.type === 'boolean')       template[name] = false;
                else if (prop.type === 'array')         template[name] = [];
                else if (prop.properties)               template[name] = this._schemaToTemplate(prop);
                else                                    template[name] = null;
            }
        }
        return template;
    }

    _buildRequestPreview() {
        if (!this.tryOutOverlay || !this.currentEndpoint) return '';
        const allFields  = this.tryOutOverlay._fields     || [];
        const bodyWidget = this.tryOutOverlay._bodyWidget || null;
        const { method, endpointPath } = this.currentEndpoint;

        const pathValues  = {};
        const queryValues = {};
        for (const { param, widget } of allFields) {
            const val = widget.getValue().trim();
            if (param.in === 'path')  pathValues[param.name]  = val;
            if (param.in === 'query') queryValues[param.name] = val;
        }

        const builtPath = endpointPath.replace(/\{(\w+)\}/g, (_m, name) =>
            pathValues[name] ? encodeURIComponent(pathValues[name]) : `{${name}}`
        );

        const queryArr = Object.entries(queryValues)
            .filter(([, v]) => v !== '')
            .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);
        const qs = queryArr.length > 0 ? '?' + queryArr.join('&') : '';

        let preview = `${method} ${builtPath}${qs}`;
        if (bodyWidget) {
            const bodyStr = bodyWidget.getValue();
            if (bodyStr?.trim()) {
                preview += '\n\nBody:\n' + bodyStr;
            }
        }
        return preview;
    }

    async _executeTryOut(allFields, bodyWidget) {
        const { portal, method, endpointPath } = this.currentEndpoint;
        const pathValues  = {};
        const queryValues = {};

        for (const { param, widget } of allFields) {
            const val = widget.getValue().trim();
            if (param.in === 'path')  pathValues[param.name]  = val;
            if (param.in === 'query') queryValues[param.name] = val;
        }

        const bodyStr = bodyWidget ? bodyWidget.getValue() : null;

        // Fermer l'overlay et le backdrop
        this.tryOutOverlay.destroy();
        this.tryOutOverlay = null;
        if (this._tryOutBlocker) { this._tryOutBlocker.destroy(); this._tryOutBlocker = null; }

        // Afficher "Executing..."
        this.detailPanel.setContent('\n  {yellow-fg}Executing...{/yellow-fg}');
        this.screen.render();

        try {
            const { status, data } = await this.executeEndpoint(
                portal, method, endpointPath, pathValues, queryValues, bodyStr
            );
            this._showResponse(status, data, method, endpointPath);
        } catch (err) {
            // Cas exceptionnel uniquement (executeEndpoint retourne toujours { status, data })
            const msg = this._escapeBraces(err?.message || String(err));
            this.detailPanel.setContent(`\n  {red-fg}Error: ${msg}{/red-fg}\n\n  Press any key or navigate the tree to continue.`);
            this.logBar.setContent(` {red-fg}Request failed: ${msg}{/red-fg}`);
            // Restaurer le focus sur l'arbre pour que Tab/navigation fonctionnent
            this.focusedPanel = 'tree';
            this.tree.focus();
            this._updateFocusStyle();
            this.screen.render();
        }
    }

    _showResponse(statusCode, data, method, endpointPath) {
        const json = typeof data === 'string' ? data : JSON.stringify(data, null, 2);

        // Box dédiée avec tags:false pour afficher le JSON brut sans interférence blessed
        const responseBox = blessed.box({
            parent: this.screen,
            top: 3,
            left: 0,
            width: '100%',
            height: '100%-6',
            tags: false,
            border: { type: 'line' },
            label: ` Response (Esc/q: close) `,
            scrollable: true,
            alwaysScroll: true,
            keys: true,
            vi: true,
            mouse: true,
            style: {
                fg: 'white',
                border: { fg: statusCode < 300 ? 'green' : statusCode < 500 ? 'yellow' : 'red' }
            },
            scrollbar: { ch: ' ', track: { bg: 'yellow' }, style: { inverse: true } },
            content: `  HTTP ${statusCode}  ${method} ${endpointPath}\n\n${json}`
        });

        this._responseBox = responseBox;
        responseBox.key(['escape', 'q'], () => this._closeResponseBox());
        responseBox.focus();

        this.logBar.setContent(` HTTP ${statusCode}  ${method} ${endpointPath} | Esc: close response`);
        this.screen.render();
    }

    _escapeBraces(str) {
        if (!str) return '';
        return str.replace(/\{/g, '(').replace(/\}/g, ')');
    }

    _formatSchema(schema, indent = 1, compact = false) {
        if (!schema?.properties) return '';
        let content = '';
        const pad = ' '.repeat(indent * 2 + 1);
        const required = new Set(schema.required || []);
        for (const [name, prop] of Object.entries(schema.properties)) {
            const req = required.has(name) ? '{red-fg}*{/red-fg}' : ' ';
            const enumStr = prop.enum ? ` [${prop.enum.join('|')}]` : '';
            content += `${pad}${req} {cyan-fg}${name}{/cyan-fg} (${prop.type || 'object'})${enumStr}\n`;
            if (!compact && prop.description) {
                content += `${pad}    ${this._escapeBraces(prop.description)}\n`;
            }
            if (prop.properties) {
                content += this._formatSchema(prop, indent + 1, compact);
            }
        }
        return content;
    }

    _flattenTree(node) {
        let nodes = [];
        if (node.name) nodes.push(node);
        if (node.children) {
            for (const key of Object.keys(node.children)) {
                nodes = nodes.concat(this._flattenTree(node.children[key]));
            }
        }
        return nodes;
    }

    _buildNodeMap(node) {
        if (node.portal && node.method && node.endpointPath) {
            this._nodeMap.set(node.name, node);
        }
        if (node.children) {
            for (const key of Object.keys(node.children)) {
                this._buildNodeMap(node.children[key]);
            }
        }
    }

    /**
     * Expand all ancestor branches leading to target node so it becomes visible.
     * Returns true if the target was found in the tree.
     */
    _expandPathTo(node, target) {
        if (node === target) return true;
        if (node.children) {
            for (const key of Object.keys(node.children)) {
                if (this._expandPathTo(node.children[key], target)) {
                    node.extended = true;
                    return true;
                }
            }
        }
        return false;
    }

    _findNext() {
        const keyword = this.searchKeyword.toLowerCase();
        let startIndex = this.lastSearchIndex + 1;
        let foundIndex = -1;

        for (let i = startIndex; i < this.flatNodeList.length; i++) {
            if (this.flatNodeList[i].name.toLowerCase().includes(keyword)) {
                foundIndex = i;
                break;
            }
        }

        if (foundIndex === -1 && startIndex > 0) {
            for (let i = 0; i < startIndex; i++) {
                if (this.flatNodeList[i].name.toLowerCase().includes(keyword)) {
                    foundIndex = i;
                    break;
                }
            }
        }

        if (foundIndex !== -1) {
            this.lastSearchIndex = foundIndex;
            const targetNode = this.flatNodeList[foundIndex];

            // Expand parent branches so the target becomes visible in the tree
            this._expandPathTo(this.treeData, targetNode);
            this.tree.setData(this.treeData);

            // Now find and select the target in the visible items
            const items = this.tree.rows.items;
            for (let i = 0; i < items.length; i++) {
                if (items[i].content.includes(targetNode.name)) {
                    this.tree.rows.select(i);
                    break;
                }
            }

            // Show endpoint details if it's a leaf node
            if (targetNode.portal && targetNode.method && targetNode.endpointPath) {
                this._showEndpointDetails(targetNode.portal, targetNode.method, targetNode.endpointPath);
            }

            const matchNum = this.flatNodeList.slice(0, foundIndex + 1).filter(n => n.name.toLowerCase().includes(keyword)).length;
            const total = this.flatNodeList.filter(n => n.name.toLowerCase().includes(keyword)).length;
            this.logBar.setContent(` Search: "${this.searchKeyword}" - Match ${matchNum} of ${total}`);
        } else {
            this.logBar.setContent(` {yellow-fg}No results found for "${this.searchKeyword}"{/yellow-fg}`);
        }
        this.screen.render();
    }
}
