/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import pgAdmin from 'sources/pgadmin';
import Menu, { MenuItem } from '../../../static/js/helpers/Menu';
import getApiInstance from '../../../static/js/api_instance';
import url_for from 'sources/url_for';
import withCheckPermission from './withCheckPermission';
import usePreferences from '../../../preferences/static/js/store';

const MAIN_MENUS = [
  { label: gettext('File'), name: 'file', id: 'mnu_file', index: 0, addSeprator: true, hasDynamicMenuItems: false },
  { label: gettext('Object'), name: 'object', id: 'mnu_obj', index: 1, addSeprator: true, hasDynamicMenuItems: true },
  { label: gettext('Tools'), name: 'tools', id: 'mnu_tools', index: 2, addSeprator: true, hasDynamicMenuItems: false },
  { label: gettext('Help'), name: 'help', id: 'mnu_help', index: 5, addSeprator: false, hasDynamicMenuItems: false }
];

export default class MainMenuFactory {
  static electronCallbacks = {};

  static toElectron() {
    // we support 2 levels of submenu
    return pgAdmin.Browser.MainMenus.map((m)=>{
      return {
        ...m.serialize(),
        submenu: m.menuItems.map((sm)=>{
          const smName = `${m.name}_${sm.name}`;
          MainMenuFactory.electronCallbacks[smName] = sm.callback;
          return {
            ...sm.serialize(),
            submenu: sm.getMenuItems()?.map((smsm)=>{
              MainMenuFactory.electronCallbacks[`${smName}_${smsm.name}`] = smsm.callback;
              return {
                ...smsm.serialize(),
              };
            })
          };
        })
      };
    });
  }

  static listenToElectronMenuClick() {
    window.electronUI?.onMenuClick((menuName)=>{
      MainMenuFactory.electronCallbacks[menuName]?.();
    });
  }

  static createMainMenus() {
    pgAdmin.Browser.MainMenus = [];
    MAIN_MENUS.forEach((_menu) => {
      let menuObj = Menu.create(_menu.name, _menu.label, _menu.id, _menu.index, _menu.addSeprator, _menu.hasDynamicMenuItems);
      pgAdmin.Browser.MainMenus.push(menuObj);
      // Don't add menuItems for hasDynamicMenuItems true as it's menuItems get changed on tree selection.
      if(!_menu.hasDynamicMenuItems) {
        menuObj.clearMenuItems();
        menuObj.addMenuItems(MainMenuFactory.createMenuItems(pgAdmin.Browser.all_menus_cache[_menu.name]));
      }
    });

    // enable disable will take care of dynamic menus.
    MainMenuFactory.enableDisableMenus();

    window.electronUI?.setMenus(MainMenuFactory.toElectron());
  }

  static getSeparator(label, priority) {
    return new MenuItem({type: 'separator', label, priority});
  }

  static createMenuItem(options) {
    const callback = () => {
      // Some callbacks registered in 'callbacks' check and call specifiec callback function
      if (options.module && 'callbacks' in options.module && options.module.callbacks[options.callback]) {
        options.module.callbacks[options.callback].apply(options.module, [options.data, pgAdmin.Browser.tree?.selected()]);
      } else if (options?.module?.[options.callback]) {
        options.module[options.callback](options.data, pgAdmin.Browser.tree?.selected());
      } else if (options?.callback) {
        options.callback(options);
      } else if (options.url != '#') {
        let api = getApiInstance();
        api(
          url_for('tools.initialize')
        ).then(()=>{
          window.open(options.url);
        }).catch(()=>{
          pgAdmin.Browser.notifier.error(gettext('Error in opening window'));
        });
      }
    };
    return new MenuItem({...options, callback: withCheckPermission(options, callback)}, (menu, item)=> {
      pgAdmin.Browser.Events.trigger('pgadmin:enable-disable-menu-items', menu, item);
      window.electronUI?.enableDisableMenuItems(menu?.serialize(), item?.serialize());
    });
  }

  static updateShortcutsFromPreferences(prefStore)  {
    const updateShortcuts = (item) => {
      if (!item || typeof item !== 'object') return;

      Object.values(item).forEach((menuItem) => {
        if (!menuItem || typeof menuItem !== 'object') return;

        if (menuItem?.shortcut_preference) {
          const [module, key] = menuItem.shortcut_preference;
          menuItem.shortcut = prefStore.getPreferences(module, key)?.value || null;
        }
        // Recurse only if it's a nested object.
        if (!menuItem.name) {
          updateShortcuts(menuItem);
        }
      });
    };
    let allMenus = pgAdmin.Browser?.all_menus_cache || {};
    Object.values(allMenus).forEach(updateShortcuts);
    MainMenuFactory.createMainMenus();
  };

  // Assign and Update menu shortcuts using preference.
  static subscribeShortcutChanges() {
    MainMenuFactory.updateShortcutsFromPreferences(usePreferences.getState());
    usePreferences.subscribe(MainMenuFactory.updateShortcutsFromPreferences);
  }

  static enableDisableMenus(item) {
    item = item || pgAdmin.Browser.tree?.selected();
    let itemData = pgAdmin.Browser.tree?.itemData(item);

    const checkForItems = (items)=>{
      items.forEach((mitem) => {
        const subItems = mitem.getMenuItems() ?? [];
        if(subItems.length > 0) {
          checkForItems(subItems);
        } else {
          mitem.checkAndSetDisabled(itemData, item);
        }
      });
    };

    // Non dynamic menus will be required to check whether enabled/disabled.
    pgAdmin.Browser.MainMenus.filter((m)=>(!m.hasDynamicMenuItems)).forEach((menu) => {
      checkForItems(menu.getMenuItems());
    });

    pgAdmin.Browser.MainMenus.filter((m)=>(m.hasDynamicMenuItems)).forEach((menu) => {
      let menuItemList = MainMenuFactory.getDynamicMenu(menu.name, item, itemData);
      menu.setMenuItems(menuItemList);
    });

    // set the context menu as well
    pgAdmin.Browser.BrowserContextMenu = MainMenuFactory.getDynamicMenu('context', item, itemData, true);

    window.electronUI?.setMenus(MainMenuFactory.toElectron());

    pgAdmin.Browser.Events.trigger('pgadmin:refresh-app-menu');
  }

  static checkNoMenuOptionForNode(itemData){
    if(!itemData) {
      return true;
    }
    let selectedNodeFromNodes=pgAdmin.Browser.Nodes[itemData._type];
    let selectedNode=pgAdmin.Browser.tree.selected();
    return selectedNodeFromNodes.showMenu?.(itemData, selectedNode) ?? true;
  }

  static createMenuItems(items, skipDisabled=false, checkAndSetDisabled=()=>true) {
    let retVal = [];
    let categories = {};

    const getNewMenuItem = (i)=>{
      const mi = MainMenuFactory.createMenuItem({...i});
      checkAndSetDisabled?.(mi);
      if(skipDisabled && mi.isDisabled) {
        return null;
      }
      return mi;
    };

    const getMenuCategory = (catName)=>{
      let category = pgAdmin.Browser.menu_categories[catName];

      if(!category) {
        // generate category on the fly.
        category = {
          name: catName,
          label: catName,
          priority: 10,
        };
      }

      let cmi = categories[category.name];
      if(!cmi) {
        cmi = getNewMenuItem({...category});
        // for easily finding again, note down.
        categories[category.name] = cmi;
      }
      return cmi;
    };

    const applySeparators = (mi)=>{
      const newItems = [];
      if(mi.above) {
        newItems.push(MainMenuFactory.getSeparator(mi.label, mi.priority));
      }
      newItems.push(mi);
      if(mi.below) {
        newItems.push(MainMenuFactory.getSeparator(mi.label, mi.priority));
      }
      return newItems;
    };

    Object.entries(items).forEach(([k, i])=>{
      if('name' in i) {
        const mi = getNewMenuItem(i);
        if(!mi) return;

        if((i.category??'common') != 'common') {
          const cmi = getMenuCategory(i.category);
          if(cmi) {
            cmi.addMenuItems([...applySeparators(mi)]);
          } else {
            retVal.push(...applySeparators(mi));
          }
        } else {
          retVal.push(...applySeparators(getNewMenuItem(i)));
        }
      } else {
        // Can be a category
        const cmi = getMenuCategory(k);
        if(cmi) {
          cmi.addMenuItems(MainMenuFactory.createMenuItems(i, skipDisabled, checkAndSetDisabled));
        }
      }
    });

    // Push the category menus
    Object.values(categories).forEach((cmi)=>{
      const items = cmi.getMenuItems();

      // if there is only one menu in the category, then no need of the category.
      if(items.length <= 1 && !cmi.single) {
        retVal = retVal.concat(items);
        return;
      }
      retVal.push(...applySeparators(cmi));
    });

    Menu.sortMenus(retVal ?? []);
    return retVal;
  }

  static getDynamicMenu(name, item, itemData, skipDisabled=false) {
    if(!item) {
      return [MainMenuFactory.createMenuItem({
        name: '',
        label: gettext('No object selected'),
        category: 'create',
        priority: 1,
        enable: false,
      })];
    }
    const showMenu = MainMenuFactory.checkNoMenuOptionForNode(itemData);
    if(!showMenu){
      return [MainMenuFactory.createMenuItem({
        enable : false,
        label: gettext('No menu available for this object.'),
        name:'',
        priority: 1,
        category: 'create',
      })];
    } else {
      const nodeTypeMenus = pgAdmin.Browser.all_menus_cache[name]?.[itemData._type] ?? [];
      const menuItemList = MainMenuFactory.createMenuItems(nodeTypeMenus, skipDisabled, (mi)=>{
        return mi.checkAndSetDisabled(itemData, item);
      });
      if(menuItemList.length == 0) {
        return [MainMenuFactory.createMenuItem({
          enable : false,
          label: gettext('No menu available for this object.'),
          name:'',
          priority: 1,
          category: 'create',
        })];
      }
      return menuItemList;
    }
  }
}
