import blessed from 'blessed';
import contrib from 'blessed-contrib';
import { JSONViewer } from './JSONViewer.js';

export class InteractiveDisplay {
    constructor(program, logger, services) {
        this.logger = logger;
        this.services = services;
        services['interactive_display'] = this;

        this.logger.debug('InteractiveDisplay.constructor');
    }

    displayObjectNavigator(obj, context = '', options) {
        const screen = blessed.screen({
            smartCSR: true
        });
        
        const jsonViewer = new JSONViewer({
            screen: screen,
            obj: obj,
            fetchObject: this.fetchObjectCallback.bind(this),
            context: context,
            reqoptions: options
        });

        screen.render();
    }

    async fetchObjectCallback(key, value, context = '', options = {}) {
        if (key === "id") {
            if (context === "user") key = "userId";
            if (context === "company") key = "companyId";
        }
        if (key === "userId" || key === "ownerId") return await this.services['admin'].getUser(value, {});
        if (key === "companyId") return await this.services['admin'].getCompany(value, {});
    

        if (context === "users") return await this.services['admin'].getUsers(options);
        if (context === "companies") return await this.services['admin'].getCompanies(options);
        if (context === "applications") return await this.services['applications'].getApplications(options);

        return null;
    }

    /**
     * Affiche une représentation interactive en plein écran d'un objet JavaScript
     * dans le terminal. L'utilisateur peut naviguer, ouvrir/fermer des branches
     * et fermer la vue.
     *
     * @param {object} obj L'objet à représenter.
     */
    displayInteractiveObjectTree(obj, title = undefined) {
        // Crée l'écran principal
        const screen = blessed.screen({
            smartCSR: true,
            title: (title ? title : 'Object Tree Viewer')
        });


        // Crée la boîte pour afficher le chemin (10% du haut)
        const pathBox = blessed.box({
            parent: screen,
            top: 0,
            left: 0,
            width: '100%',
            height: '5%',
            content: 'Current path: ',
            tags: true,
            border: {
                type: 'line'
            },
            style: {
                fg: 'white',
                bg: 'blue',
                border: {
                    fg: '#f0f0f0'
                }
            }
        });


        // Crée l'arbre (90% du bas)
        const tree = contrib.tree({
            parent: screen,
            top: '5%',
            left: 0,
            width: '100%',
            height: '85%',
            //keys: true,
            vi: true,
            mouse: true,
            tags: true,
            border: {
                type: 'line'
            },
            style: {
                fg: 'green',
                item: {
                    fg: 'white',
                    hover: {
                        bg: 'blue'
                    }
                },
                selected: {
                    bg: 'blue'
                }
            }
        });

        // Crée le logger (10% du bas)
        const logger = blessed.log({
            parent: screen,
            top: '90%',
            width: '100%',
            height: '10%',
            border: 'line',
            tags: true,
            keys: true,
            vi: true,
            mouse: true,
            scrollback: 100,
            scrollbar: {
                ch: ' ',
                track: {
                    bg: 'yellow'
                },
                style: {
                    inverse: true
                }
            }
        });

        // Crée une boîte de saisie pour la recherche
        const searchInput = blessed.textbox({
            parent: screen,
            bottom: 0,
            left: 0,
            width: '100%',
            height: 3,
            inputOnFocus: true,
            hidden: true,
            style: {
                fg: 'white',
                bg: 'blue'
            },
            border: {
                type: 'line'
            },
            label: ' Recherche (Entrée pour valider, Échap pour annuler) '
        });

        // Crée une boîte d'affichage de l'aide
        const helpBox = blessed.box({
            parent: screen,
            top: 'center',
            left: 'center',
            width: '50%',
            height: '50%',
            align: 'left',
            valign: 'middle',
            border: 'line',
            tags: true,
            style: {
                fg: 'white',
                bg: 'black',
                border: {
                    fg: 'white'
                }
            },
            content: `
            {bold}Help{/bold}
            --------------------------------------

            {bold}Navigation{/bold}
            - Up / Down: move into the tree
            - Space / ENTER: Open / Close a branch

            {bold}Search{/bold}
            - / : Open search
            - ENTER: Submit search
            - n: Go to next result
            - ESC: Cancel search and close search window

            {bold}General{/bold}
            - F1 or ? : Display / hide this help window
            - ESC / q / C-C : Quit application
            `,
            label: 'Help (ESC to hide)',
            hidden: true
        });


        // Construit la structure de l'arbre à partir de l'objet
        function buildTree(data, labelPrefix = '', parentPath = '') {
            let id = 1;
            const nodes = {};
            for (const key in data) {
                if (Object.prototype.hasOwnProperty.call(data, key)) {
                    const value = data[key];

                    const isObject = typeof value === 'object' && value !== null;

                    const label = `${labelPrefix}${key}:`;

                    const path = parentPath + `/${label}`;
                    if (isObject) {
                        nodes[label] = {
                            name: `${label}`,
                            extended: true,
                            children: buildTree(value, '  ' + labelPrefix, path),
                            id: id,
                            path: path
                        };
                    } else {
                        nodes[`${label} ${JSON.stringify(value)}`] = {
                            name: `${label} ${JSON.stringify(value)}`,
                            id: id,
                            path: path
                        };
                    }
                    id++;
                }
            }
            return nodes;
        }

        const treeData = {
            name: 'root',
            extended: true,
            children: buildTree(obj),
            id : 0,
            path: '/'
        };

        // Définit les données de l'arbre
        tree.setData(treeData);
        let searchKeyword = '';
        let lastSearchIndex = -1;
        let flatNodeList = [];


        /**
        * Fonction récursive pour parcourir l'arbre de données et créer une liste plate.
        * @param {object} node Le nœud à partir duquel commencer le parcours.
        * @returns {Array<object>} Une liste plate de tous les nœuds de l'arbre.
        */
        function flattenTreeData(node) {
            let nodes = [];
            if (node.name) {
                nodes.push(node);
            }
            if (node.children) {
                for (const childKey in node.children) {
                    nodes = nodes.concat(flattenTreeData(node.children[childKey]));
                }
            }
            return nodes;
        }

        // Crée la liste plate au démarrage
        flatNodeList = flattenTreeData(treeData);

        // Fonction utilitaire pour trouver un nœud dans la structure de données à partir de son nom
        function findNodeByName(root, name) {
            logger.log(`findNodeByName ${root.name} ${name}`);
            if (root.name === name) return root;
            if (root.children) {
                for (let childName in root.children) {
                    const result = findNodeByName(root.children[childName], name);
                    if (result) return result;
                }
            }
            return null;
        }

        // Fonction pour reconstruire le chemin à partir d'un nœud
        function getPathFromNode(node, root) {
            let pathItems = [];
            let currentNode = node;

            // Remonter la hiérarchie pour construire le chemin
            while (currentNode && currentNode.name !== root.name) {
                pathItems.unshift(currentNode.name);
                // Trouver le parent dans la structure de données d'origine
                let parentNode = null;
                function findParent(tree, childName) {
                    if (tree.children) {
                        for (let key in tree.children) {
                            if (tree.children[key].name === childName) {
                                return tree;
                            }
                            let found = findParent(tree.children[key], childName);
                            if (found) return found;
                        }
                    }
                    return null;
                }
                currentNode = findParent(root, currentNode.name);
            }
            return ['/'].concat(pathItems);
        }

/*
        // Gère la mise à jour du chemin quand la sélection de l'arbre change
        // NOTE : Nous écoutons l'événement 'select' sur le composant liste interne
        tree.rows.on('select', (item, index) => {
            logger.log('move in list');
            // Le contenu de l'item a un format "  nom du noeud"
            const nodeName = item.content.trim();

            // On trouve le nœud correspondant dans notre structure de données d'origine
            const selectedNode = findNodeByName(treeData, nodeName);

            if (selectedNode) {
                const pathItems = getPathFromNode(selectedNode, treeData);
                pathList.setItems(pathItems);
                screen.render();
            }
        });
*/
        // Fonction de mise à jour du chemin
        function updatePath() {
            // Récupérer le contenu du nœud sélectionné dans la liste visible
            const selectedItem = tree.rows.items[tree.rows.selected];
            if (selectedItem) {
                const nodeName = selectedItem.content.trim();
                const selectedNode = findNodeByName(treeData, nodeName);
                if (selectedNode) {
                    const pathItems = selectedNode.path;
                    //const pathItems = getPathFromNode(selectedNode, treeData);
                    pathBox.setContent(`Chemin actuel: {bold}${pathItems}{/bold}`);
                }
            }
            screen.render();
        }

        // Gère la mise à jour du chemin quand la sélection de l'arbre change
        tree.rows.on('keypress', (ch, key) => {
            // Écouter les flèches haut et bas
            if (key.name === 'up' || key.name === 'down') {
                // `nextTick` garantit que l'index de sélection a été mis à jour par blessed avant de lire `tree.rows.selected`
                process.nextTick(updatePath);
            }
        });
        /*
        // Gère la mise à jour du chemin
        tree.on('select', (node) => {
            let path = '';
            let currentNode = node;

            // Remonte la hiérarchie pour construire le chemin
            while (currentNode && currentNode.name !== 'root') {
                path = '/' + currentNode.name + path;
                currentNode = currentNode.parent;
            }
            path = path || '/';

            // Met à jour la boîte du chemin
            pathBox.setContent(`Chemin actuel: {bold}${path}{/bold}`);
            screen.render();
        });
        */

        /**
        * Fonction pour trouver et sélectionner le prochain nœud
        * qui correspond au mot-clé de recherche.
        * @param {string} keyword Le terme de recherche.
        */
        function findNextNode(keyword) {
            logger.log(`findNextNode ${keyword}`);
            let startIndex = lastSearchIndex + 1;
            let foundIndex = -1;

            // Recherche à partir de l'index suivant
            for (let i = startIndex; i < flatNodeList.length; i++) {
                const node = flatNodeList[i];
                if (node.name.toLowerCase().includes(keyword.toLowerCase())) {
                    foundIndex = i;
                    break;
                }
            }

            // Si rien n'est trouvé, recommencer depuis le début
            if (foundIndex === -1 && startIndex > 0) {
                for (let i = 0; i < startIndex; i++) {
                    const node = flatNodeList[i];
                    if (node.name.toLowerCase().includes(keyword.toLowerCase())) {
                        foundIndex = i;
                        break;
                    }
                }
            }

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

                // Pour sélectionner le nœud, nous devons d'abord nous assurer qu'il est visible.
                // L'idée est de trouver le chemin vers ce nœud et d'ouvrir tous les parents.
                let currentNode = targetNode;
                let path = [];
                while (currentNode && currentNode.name !== 'root') {
                    path.unshift(currentNode.name);
                    // Find the parent by iterating through the original treeData
                    let parentNode = null;
                    function findParent(tree, childName) {
                        if (tree.children) {
                            for (let key in tree.children) {
                                if (tree.children[key].name === childName) {
                                    return tree;
                                }
                                let found = findParent(tree.children[key], childName);
                                if (found) return found;
                            }
                        }
                        return null;
                    }
                    currentNode = findParent(treeData, currentNode.name);
                }

                // Now, open all the necessary branches in the blessed tree
                // Note: there is no direct method to open branches programmatically.
                // We can simulate the expansion by interacting with the tree's internal structure.

                // To reliably find the item to select, we must use its content, which
                // is a concatenation of indents and the node name.
                const treeItems = tree.rows.items;

                // Find the actual line content for the targetNode
                let targetLineContent = '';
                for (let i = 0; i < treeItems.length; i++) {
                    const itemContent = treeItems[i].content;
                    logger.log(`itemContent ${itemContent} - targetNode.name ${targetNode.name}`);
                    // The item content has leading spaces for hierarchy
                    //if (itemContent.trim() === targetNode.name) {
                    if (itemContent.includes(targetNode.name)) {
                        targetLineContent = itemContent;
                        break;
                    }
                }
                logger.log(`targetLineContent ${targetLineContent}`);

                const visibleIndex = tree.rows.items.findIndex(item => item.content === targetLineContent);
                if (visibleIndex !== -1) {
                    // Affectez directement la propriété `selected` et rendez l'écran
                    tree.rows.select(visibleIndex);
                }

                screen.render();
            } else {
                // Affiche un message si rien n'est trouvé
                logger.log('Aucun résultat trouvé.');
                screen.render();
            }
        }


        // Intercepte l'appui sur '/' pour lancer la recherche
        screen.key('/', () => {
            searchInput.show();
            searchInput.focus();
            screen.render();
        });

        // Intercepte la touche 'n' pour la prochaine occurrence
        screen.key('n', () => {
            if (searchKeyword) {
                findNextNode(searchKeyword);
            }
        });

        // Gère la validation de la recherche (touche Entrée)
        searchInput.on('submit', (value) => {
            searchKeyword = value;
            lastSearchIndex = -1; // Réinitialise l'index pour une nouvelle recherche
            findNextNode(searchKeyword);
            searchInput.hide();
            screen.render();
        });

        // Gère l'annulation de la recherche (touche Échap)
        searchInput.on('cancel', () => {
            searchInput.hide();
            tree.focus();
            screen.render();
        });


        // Gère l'affichage de la fenêtre d'aide
        screen.key(['f1', '?'], () => {
            if (helpBox.hidden) {
                helpBox.show();
                screen.render();
            } else {
                helpBox.hide();
                screen.render();
            }
        });

        // Gère la fermeture de l'application
        screen.key(['escape', 'q', 'C-c'], () => {
            // Si la boîte d'aide ou de recherche est visible, la cacher
            if (!helpBox.hidden) {
                helpBox.hide();
                screen.render();
            } else if (!searchInput.hidden) {
                searchInput.hide();
                screen.render();
            } else {
                // Sinon, quitter l'application
                process.exit(0);
            }
        });

        tree.focus();

        // Rend l'écran
        screen.render();
    }
}