/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

import _ from 'lodash';
import url_for from 'sources/url_for';
import gettext from 'sources/gettext';
import pgAdmin from 'sources/pgadmin';
import getApiInstance, { callFetch } from '../api_instance';
import usePreferences from '../../../preferences/static/js/store';

export const pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};


export const browserTreeState = pgBrowser.browserTreeState = pgBrowser.browserTreeState || {};

_.extend(pgBrowser.browserTreeState, {

  // Parent node to start saving / reloading the tree state
  parent: 'server',

  // The original parent of the browser tree
  orig_parent: 'server_group',

  // Stored tree state
  // Sample Object
  //  {1:
  //    'paths': [
  //      server_group/1,/server/1,/coll-database/1,/database/1,
  //      server_group/1,/server/1,/coll-database/1,/database/2,
  //    ],
  //    'selected': {
  //       'server/1': 'database/2',
  //       'database/1': 'table/1',
  //     },
  //    'conn_status': {
  //      'database/1': 1,
  //      'database/2': 0 ,
  //    },
  //  }
  // Here key is server ID

  stored_state: {},

  // Previous tree state
  last_state: {},

  is_selected: false,

  // Current tree state
  current_state: {},

  init: function() {

    let saveIntervalId, offExpandFromPrevState, offRemoveFromTreeState, offUpdateTreeState;
    usePreferences.subscribe((prefStore)=>{
      // Subscribe to listen for preferences change
      const save_tree_state_period = prefStore.getPreferences('browser', 'browser_tree_state_save_interval')?.value;
      if (saveIntervalId) {
        clearInterval(saveIntervalId);
        saveIntervalId = null;
        offEventListener();
      }
      if (!_.isUndefined(save_tree_state_period) &&  save_tree_state_period !== -1) {
        // Save the tree state at given save_tree_state_period
        saveIntervalId = setInterval(this.save_state, (save_tree_state_period) * 1000);
        this.fetch_state.apply(this);
        onEventListener();
      } else if (!_.isUndefined(save_tree_state_period)) {
        offEventListener();
        getApiInstance().delete(url_for('settings.reset_tree_state'))
          .catch(function(error) {
            console.warn(
              gettext('Error resetting the tree saved state."'), error);
          });
      }
    });

    const onEventListener = () => {
      // Register event listener
      offExpandFromPrevState = pgBrowser.Events.on('pgadmin:browser:tree:expand-from-previous-tree-state',
        this.expand_from_previous_state.bind(this));
      offRemoveFromTreeState = pgBrowser.Events.on('pgadmin:browser:tree:remove-from-tree-state',
        this.remove_from_cache.bind(this));
      offUpdateTreeState = pgBrowser.Events.on('pgadmin:browser:tree:update-tree-state',
        this.update_cache.bind(this));
    };

    const offEventListener = () => {
      // Deregister event listener
      offExpandFromPrevState?.();
      offRemoveFromTreeState?.();
      offUpdateTreeState?.();
    };

  },
  save_state: function() {

    let self = pgBrowser.browserTreeState;
    if(self.last_state == JSON.stringify(self.current_state))
      return;

    /* Using fetch with keepalive as the browser may
    cancel the axios request on tab close. keepalive will
    make sure the request is completed */
    return callFetch(
      url_for('settings.save_tree_state'), {
        keepalive: true,
        method: 'POST',
        body: JSON.stringify(self.current_state)
      })
      .then(()=> {
        self.last_state = JSON.stringify(self.current_state);
        self.fetch_state();
      })
      .catch((error)=> {
        console.warn(
          gettext('Error resetting the tree saved state."'), error);
      });
  },
  fetch_state: function() {

    let self = this;

    getApiInstance().get(
      url_for('settings.get_tree_state'),
    ).then((res)=> {
      self.stored_state = res.data;
    }).catch(function(error) {
      console.warn(
        gettext('Error resetting the tree saved state."'), error);
    });
  },
  update_cache: function(item) {
    let data = item && pgBrowser.tree.itemData(item),
      treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),
      topParent,
      pathIDs = pgBrowser.tree.pathId(pgBrowser.tree.parent(item)),
      oldPath = pathIDs.join(),
      path = [],
      tmpIndex = -1;

    // If no parent or the server not in tree hierarchy then return
    if (!pgBrowser.tree.hasParent(item) || !(this.parent in treeHierarchy) || (data._type === 'server' && !data.connected))
      return;

    topParent = treeHierarchy[this.parent]['_id'];


    if (pgBrowser.tree.isOpen(item)) {
      // Store paths

      pathIDs.push(data.id);
      path = pathIDs.join();

      if (!(topParent in this.current_state)) {
        this.current_state[topParent] = {'paths': [], 'selected': {}, 'conn_status': {}, 'is_opened': {}};
      }

      // IF the current path is already saved then return
      let index = _.find(this.current_state[topParent]['paths'], function(tData) {
        return (tData.search(path) !== -1);
      });
      if(_.isUndefined(index)) {

        // Add / Update the current item into the tree path
        if (!_.isUndefined(this.current_state[topParent]['paths'])) {
          tmpIndex = this.current_state[topParent]['paths'].indexOf(oldPath);
        } else {
          this.current_state[topParent]['paths'] = [];
        }
        if (tmpIndex !== -1) {
          this.current_state[topParent]['paths'][tmpIndex] = path;
        }
        else {
          this.current_state[topParent]['paths'].push(path);
        }
      }

    }

    // Store current selected item and database connection status
    this.update_database_status(item);
    this.update_current_selected_item(treeHierarchy);

  },
  remove_from_cache: function(item) {
    let self= this,
      treeData = self.stored_state || {},
      data = item && pgBrowser.tree.itemData(item),
      treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);

    if (treeHierarchy === null || !pgBrowser.tree.hasParent(item) || !(treeHierarchy.hasOwnProperty(self.parent)))
      return;

    let topParent = treeHierarchy?.[self.parent]['_id'],
      origParent = treeHierarchy?.[self.orig_parent]['id'];

    this.update_database_status(item);

    if (data._type == self.parent || data._type == 'database') {
      if (treeData?.[topParent]?.['paths'] && self.current_state?.[topParent]?.['paths']) {
        treeData[topParent]['paths'] = self.current_state[topParent]['paths'];
        self.save_state();
      }
      return;
    }

    if (pgBrowser.tree.isClosed(item)) {
      let tmpTreeData =  self.current_state[topParent]['paths'],
        databaseId;

      if (treeHierarchy.hasOwnProperty('database'))
        databaseId = treeHierarchy['database']['id'];

      if (!_.isUndefined(tmpTreeData) && !_.isUndefined(tmpTreeData.length)) {
        let tcnt = 0, tmpItemDataStr;
        _.each(tmpTreeData, function(tData) {
          if (_.isUndefined(tData))
            return;

          let tmpItemData = tData.split(',');

          if (tmpItemData.indexOf(data.id) !== -1 ) {
            if (databaseId === undefined || (databaseId !== undefined && tmpItemData.indexOf(databaseId) !== -1)) {

              let index = tmpItemData.indexOf(data.id);
              tmpItemData.splice(index);
              tmpItemDataStr = tmpItemData.join();

              if (tmpItemDataStr == origParent)
                self.current_state[topParent]['paths'].splice(tData, 1);
              else
                self.current_state[topParent]['paths'][tcnt] = tmpItemDataStr;
            }
          }
          tcnt ++;
        });
      }
    }
  },
  expand_from_previous_state: function(item) {
    let self = this,
      treeData = this.stored_state || {},
      data = item && pgBrowser.tree.itemData(item),
      treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);


    if (treeHierarchy === null || !pgBrowser.tree.hasParent(item) || !(treeHierarchy.hasOwnProperty(self.parent)))
      return;

    // If the server node is open then only we should populate the tree
    if (data['_type'] == self.parent && (pgBrowser.tree.isOpen(item) === false))
      return;

    let tmpTreeData = treeData[treeHierarchy[self.parent]['_id']];


    // If the server node is open then only we should populate the tree
    if (data['_type'] == 'database' && tmpTreeData && 'conn_status' in tmpTreeData && 'is_opened' in tmpTreeData &&
     (tmpTreeData['conn_status'][data['id']] === 0 || tmpTreeData['is_opened'][data['id']] === 0 ||
      !(data['id'] in tmpTreeData['is_opened'])))
      return;


    if (!_.isUndefined(tmpTreeData) && ('paths' in tmpTreeData) && !_.isUndefined(tmpTreeData['paths'].length)) {
      let tmpTreeDataPaths = [...tmpTreeData['paths']],
        databaseId;

      if (treeHierarchy.hasOwnProperty('database'))
        databaseId = treeHierarchy['database']['id'];

      _.each(tmpTreeDataPaths, function(tData) {
        if (_.isUndefined(tData))
          return;

        let tmpItemData = tData.split(',');

        // If the item is in the lastTreeState then open it
        if (tmpItemData.indexOf(data.id) !== -1) {
          if (databaseId === undefined ||  (databaseId !== undefined && tmpItemData.indexOf(databaseId) !== -1)) {

            let index = tmpItemData.indexOf(data.id);

            pgBrowser.tree.open(item);
            pgBrowser.tree.ensureLoaded(item);
            if (index == (tmpItemData.length - 1 )) {
              let tIndex = treeData[treeHierarchy[self.parent]['_id']]['paths'].indexOf(tData);
              treeData[treeHierarchy[self.parent]['_id']]['paths'].splice(tIndex, 1);
            }
          }
        }
      });
    }

    this.select_tree_item(item);

  },
  update_database_status: function(item) {
    let data = item && pgBrowser.tree.itemData(item),
      treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item);

    if (treeHierarchy.hasOwnProperty('database')) {
      let databaseItem = treeHierarchy['database']['id'],
        topParent = treeHierarchy?.[this.parent]['_id'];

      if (topParent in this.current_state && 'selected' in this.current_state[topParent]) {
        if (treeHierarchy['database'].connected) {
          this.current_state[topParent]['conn_status'][databaseItem] = 1;
        }
        else {
          this.current_state[topParent]['conn_status'][databaseItem] = 0;
        }

        if(data._type == 'database') {
          if (pgBrowser.tree.isOpen(item)) {
            this.current_state[topParent]['is_opened'][databaseItem] = 1;
          }
          else {
            this.current_state[topParent]['is_opened'][databaseItem] = 0;
          }
        }
      }
    }
  },
  update_current_selected_item(treeHierarchy) {
    if (!(this.parent in treeHierarchy))
      return;

    let topParent = treeHierarchy?.[this.parent]['_id'],
      selectedItem = pgBrowser.tree.itemData(pgBrowser.tree.selected()),
      databaseItem;

    selectedItem = selectedItem ? selectedItem.id : undefined;

    if (treeHierarchy.hasOwnProperty('database')) {
      databaseItem = treeHierarchy['database']['id'];
    }

    if (topParent in this.current_state && 'selected' in this.current_state[topParent]
    && !_.isUndefined(selectedItem)) {
      this.current_state[topParent]['selected'][treeHierarchy[this.parent]['id']] = selectedItem;

      if (!_.isUndefined(databaseItem))
        this.current_state[topParent]['selected'][databaseItem] = selectedItem;
    }
  },
  select_tree_item(item) {
    let treeData = this.stored_state || {},
      data = item && pgBrowser.tree.itemData(item),
      treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item),
      tmpTreeData = treeData[treeHierarchy[this.parent]['_id']];

    if (treeHierarchy.hasOwnProperty('server')) {
      let selectedItem = treeHierarchy['server']['id'];

      if (tmpTreeData && 'selected' in tmpTreeData && selectedItem in tmpTreeData['selected']) {
        if (tmpTreeData['selected'][selectedItem] == data.id) {
          this.is_selected = true;
          pgBrowser.tree.select(item, true, 'center');
        }
      }
    }
  },
});
