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

import { getRandomInt, hasBinariesConfiguration, getServerColors } from 'sources/utils';
import { retrieveAncestorOfTypeServer } from 'sources/tree/tree_utils';
import { generateTitle } from 'tools/sqleditor/static/js/sqleditor_title';
import { AllPermissionTypes, BROWSER_PANELS, WORKSPACES } from '../../../../browser/static/js/constants';
import usePreferences,{ listenPreferenceBroadcast } from '../../../../preferences/static/js/store';
import 'pgadmin.browser.keyboard';
import pgWindow from 'sources/window';
import pgAdmin from 'sources/pgadmin';
import pgBrowser from 'pgadmin.browser';
import PsqlComponent from './components/PsqlComponent';
import { PgAdminProvider } from '../../../../static/js/PgAdminProvider';
import getApiInstance from '../../../../static/js/api_instance';
import gettext from 'sources/gettext';
import url_for from 'sources/url_for';
import Theme from '../../../../static/js/Theme';
import { NotifierProvider } from '../../../../static/js/helpers/Notifier';
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
import * as csrfToken from 'sources/csrf';
import { ApplicationStateProvider } from '../../../../settings/static/ApplicationStateProvider';
import ToolErrorView from '../../../../static/js/ToolErrorView';
import ReactDOM from 'react-dom/client';


export default class Psql {
  static instance;

  static getInstance(...args) {
    if (!Psql.instance) {
      Psql.instance = new Psql(...args);
    }
    return Psql.instance;
  }

  constructor(pgAdmin, pgBrowser) {
    this.pgAdmin = pgAdmin;
    this.pgBrowser = pgBrowser;
    this.api = getApiInstance();
  }

  /* Enable/disable PSQL tool menu in tools based
    * on node selected. if selected node is present
    * in unsupported_nodes, menu will be disabled
    * otherwise enabled.
    */
  psqlToolEnabled(obj) {

    let isEnabled = (() => {
      if (!_.isUndefined(obj) && !_.isNull(obj) && pgAdmin['enable_psql']) {
        if (_.indexOf(pgAdmin.unsupported_nodes, obj._type) == -1) {
          if (obj._type == 'database' && obj.allowConn) {
            return true;
          } else if (obj._type != 'database') {
            return true;
          } else {
            return false;
          }
        } else {
          return false;
        }
      } else {
        return false;
      }
    })();

    return isEnabled;
  }

  init() {
    if (this.initialized)
      return;
    this.initialized = true;
    csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
    // Define the nodes on which the menus to be appear

    let menus = [{
      name: 'psql',
      module: this,
      applies: ['tools'],
      callback: 'openPsqlTool',
      enable: this.psqlToolEnabled,
      priority: 1,
      label: gettext('PSQL Tool'),
      data:{
        applies: 'tools',
        data_disabled: gettext('Please select a database from the object explorer to access the PSQL Tool.'),
      },
      permission: AllPermissionTypes.TOOLS_PSQL_TOOL,
    }];


    this.enable_psql_tool = pgAdmin['enable_psql'];
    if(pgAdmin['enable_psql']) {
      pgBrowser.add_menus(menus);
    }
  }

  openPsqlTool(_data, treeIdentifier, connectionInfo=null) {
    let parentData = null;
    let panelTitle = '';
    if (connectionInfo){
      parentData = {
        server_group: {
          _id: connectionInfo.sgid || 0
        },
        server: {
          _id: connectionInfo.sid,
          server_type: connectionInfo.server_type,
          label: connectionInfo.server_name,
          user: {
            name: connectionInfo.user
          }
        },
        database: {
          _id: connectionInfo.did,
          _label: connectionInfo.db
        },
        schema: {
          _id: connectionInfo.scid || null,
        },
        table: {
          _id: connectionInfo.tid || null,
        }
      };

    }else{
      const serverInformation = retrieveAncestorOfTypeServer(pgBrowser, treeIdentifier, gettext('PSQL Error'));
      if (!hasBinariesConfiguration(pgBrowser, serverInformation)) {
        return;
      }

      const node = pgBrowser.tree.findNodeByDomElement(treeIdentifier);
      if (node === undefined || !node.getData()) {
        pgAdmin.Browser.notifier.alert(
          gettext('PSQL Error'),
          gettext('No object selected.')
        );
        return;
      }

      parentData = pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
      if(_.isUndefined(parentData.server)) {
        pgAdmin.Browser.notifier.alert(
          gettext('PSQL Error'),
          gettext('Please select a server/database object.')
        );
        return;
      }

    }

    const transId = getRandomInt(1, 9999999);
    // Set psql tab title as per prefrences setting.
    let title_data = {
      'database': parentData.database ? _.unescape(parentData.database._label) : 'postgres' ,
      'username': parentData.server.user.name,
      'server': parentData.server.label,
      'type': 'psql_tool',
    };

    let tab_title_placeholder = usePreferences.getState().getPreferencesForModule('browser').psql_tab_title_placeholder;
    panelTitle = generateTitle(tab_title_placeholder, title_data);

    const [panelUrl, db_label] = this.getPanelUrls(transId, parentData);

    const open_new_tab = usePreferences.getState().getPreferencesForModule('browser').new_browser_tab_open;

    // Extract bgcolor and fgcolor from server icon
    const { bgcolor, fgcolor } = getServerColors(parentData?.server?.icon);

    pgAdmin.Browser.Events.trigger(
      'pgadmin:tool:show',
      `${BROWSER_PANELS.PSQL_TOOL}_${transId}`,
      panelUrl,
      {title: panelTitle, db: db_label, server_name: parentData.server.label, 'user': parentData.server.user.name },
      {title: panelTitle, icon: 'pg-font-icon icon-terminal', manualClose: false, renamable: true, bgcolor: bgcolor, fgcolor: fgcolor, server_id: parentData?.server?._id},
      Boolean(open_new_tab?.includes('psql_tool'))
    );

    return true;
  }

  getPanelUrls(transId, pData) {
    let openUrl = url_for('psql.panel', {
      trans_id: transId,
    });
    const misc_preferences = usePreferences.getState().getPreferencesForModule('misc');
    let theme = misc_preferences.theme;

    openUrl += `?sgid=${pData.server_group._id}`
      +`&sid=${pData.server._id}`
      +`&did=${pData.database._id}`
      +`&server_type=${pData.server.server_type}`
      + `&theme=${theme}`;

    if(pData.database?._id) {
      openUrl += `&db=${encodeURIComponent(pData.database._label)}`;
    } else {
      openUrl += `&db=${''}`;
    }

    return [openUrl, pData.database._label];
  }

  async loadComponent(container, params) {
    let panelDocker = pgWindow.pgAdmin.Browser.docker.psql_workspace;
    if (pgWindow.pgAdmin.Browser.docker.currentWorkspace == WORKSPACES.DEFAULT) {
      panelDocker = pgWindow.pgAdmin.Browser.docker.default_workspace;
    }

    pgAdmin.Browser.keyboardNavigation.init();
    await listenPreferenceBroadcast();
    const root = ReactDOM.createRoot(container);
    root.render(
      <Theme>
        <PgAdminProvider value={pgAdmin}>
          <ApplicationStateProvider>
            <ModalProvider>
              <NotifierProvider pgAdmin={pgAdmin} pgWindow={pgWindow} />
              { params.error ?
                <ToolErrorView
                  error={params.error}
                  panelId={`${BROWSER_PANELS.PSQL_TOOL}_${params.trans_id}`}
                  panelDocker={panelDocker}
                /> :
                <PsqlComponent
                  params={params}
                  pgAdmin={pgAdmin}
                  panelId={`${BROWSER_PANELS.PSQL_TOOL}_${params.trans_id}`}
                  panelDocker={panelDocker}
                />
              }
            </ModalProvider>
          </ApplicationStateProvider>
        </PgAdminProvider>
      </Theme>
    );
  }
}
