//////////////////////////////////////////////////////////////////////////////////////////
//                               ___            _     ___                               //
//                               |   |   \/    | ) |  |                                 //
//                           O-  |-  |   |  -  |   |  |-  -O                            //
//                               |   |_  |     |   |  |_                                //
//                                                                                      //
//////////////////////////////////////////////////////////////////////////////////////////

// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de>
// SPDX-License-Identifier: MIT

'use strict';

import * as utils from '../utils.js';
import {ItemClass} from '../ItemClass.js';

const Gtk                 = await utils.importInPrefsOnly('gi://Gtk');
const ConfigWidgetFactory = await utils.importInPrefsOnly('./ConfigWidgetFactory.js');
const _                   = await utils.importGettext();

// We have to import the Shell module optionally. This is because this file is included
// from both sides: From prefs.js and from extension.js. When included from prefs.js, this
// module is not available. This is not a problem, as the preferences will not call the
// createItem() methods below; they are merely interested in the menu's name, icon and
// description.
const Shell = await utils.importInShellOnly('gi://Shell');

//////////////////////////////////////////////////////////////////////////////////////////
// Returns an item with entries for all running applications. Clicking these will bring //
// the corresponding app to the foreground. Like Alt-Tab.                               //
// See common/ItemRegistry.js for a description of the action's format.                 //
//////////////////////////////////////////////////////////////////////////////////////////

export function getRunningAppsMenu() {

  // This is used as data for newly created items of this type.
  const defaultData =
      {activeWorkspaceOnly: false, appGrouping: true, hoverPeeking: true, nameRegex: ''};

  return {

    // There are two fundamental item types in Fly-Pie: Actions and Menus. Actions have an
    // onSelect() method which is called when the user selects the item, Menus can have
    // child Actions or Menus.
    class: ItemClass.MENU,

    // This will be shown in the add-new-item-popover of the settings dialog.
    name: _('Running Apps'),

    // This is also used in the add-new-item-popover.
    icon: 'flypie-menu-running-apps-symbolic-#74a',

    // Translators: Please keep this short.
    // This is the (short) description shown in the add-new-item-popover.
    subtitle: _('Shows the currently running applications.'),

    // This is the (long) description shown when an item of this type is selected.
    description: _(
        'The <b>Running Apps</b> menu shows all currently running applications. This is similar to the Alt+Tab window selection. As the entries change position frequently, this is actually not very effective.'),

    // Items of this type have several additional configuration parameter.
    config: {
      defaultData: defaultData,

      // This is called whenever an item of this type is selected in the menu editor. It
      // returns a Gtk.Widget which will be shown in the sidebar of the menu editor. The
      // currently configured data object will be passed as first parameter. The second
      // parameter is a callback which must be fired whenever the user changes something
      // in the widgets.
      getWidget(data, updateCallback) {
        // Use default data for undefined properties.
        data = {...defaultData, ...data};

        const vBox = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, spacing: 16});

        let toggles   = [];
        let nameRegex = data.nameRegex;

        // This is called whenever one of the toggles is switched or when the user enters
        // something in the name filter entry.
        const _updateData = () => {
          updateCallback({
            activeWorkspaceOnly: toggles[0].active,
            appGrouping: toggles[1].active,
            hoverPeeking: toggles[2].active,
            nameRegex: nameRegex
          });
        };

        // First create the name filter entry.
        const tooltip = _(
            'You can use this to filter the displayed windows. Regular expressions are supported: Use a simple string like "Fire" to show only windows whose titles contain "Fire" (e.g. Firefox). Use "Fire|Water" to match either "Fire" or "Water". A negation would be "^(?!.*Fire)" to match anything but "Fire". Remember to use the live preview to instantly see the results!');
        const regexEntry = ConfigWidgetFactory.createTextWidget(
            _('Window Filter'), _('See tooltip for details.'), tooltip, data.nameRegex,
            text => {
              nameRegex = text;
              _updateData();
            });
        vBox.append(regexEntry);

        // Then create the three switches.
        const _createToggle = (i, name, value) => {
          const hBox = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL, spacing: 5});
          hBox.append(
              new Gtk.Label({label: name, halign: Gtk.Align.START, hexpand: true}));

          const toggle = new Gtk.Switch({active: value, halign: Gtk.Align.END});
          hBox.append(toggle);

          toggle.connect('notify::active', () => {
            _updateData();
          });

          vBox.append(hBox);

          return toggle;
        };

        toggles[0] =
            _createToggle(0, _('Active Workspace Only'), data.activeWorkspaceOnly);
        toggles[1] = _createToggle(1, _('Group by Application'), data.appGrouping);
        toggles[2] = _createToggle(2, _('Peek on Hover'), data.hoverPeeking);

        return vBox;
      }
    },

    // This will be called whenever a menu is opened containing an item of this kind.
    createItem: (data) => {
      // Use default data for undefined properties.
      data = {...defaultData, ...data};

      // Retrieve a list of all running apps and sort it alphabetically to make the window
      // positions more deterministic.
      const apps = Shell.AppSystem.get_default().get_running();
      apps.sort((a, b) => a.get_name().localeCompare(b.get_name()));

      // This will be our menu.
      const result = {children: []};

      // Now iterate through all windows of all apps.
      apps.forEach(app => {
        let windows = app.get_windows();

        // Filter windows which do not match the regex.
        windows = windows.filter(w => (new RegExp(data.nameRegex)).test(w.title));

        // Filter windows which are not on the current workspace.
        if (data.activeWorkspaceOnly) {
          windows = windows.filter(
              w => w.get_workspace() == global.workspace_manager.get_active_workspace());
        }

        // Sort the remaining windows alphabetically.
        windows.sort((a, b) => a.title.localeCompare(b.title));

        // Get the icon for our items.
        let icon = 'image-missing';
        try {
          icon = app.get_app_info().get_icon().to_string();
        } catch (e) {
        }

        // We will add the window items directly to the result menu. Only if there are
        // more than one window for the current app and grouping is enabled, we will
        // create a submenu.
        let parentMenu = result;
        if (data.appGrouping && windows.length > 1) {
          parentMenu = {name: app.get_name(), icon: icon, children: []};
          result.children.push(parentMenu);
        }

        // Now add the actual items!
        windows.forEach(window => {
          let wasMinimized = false;

          parentMenu.children.push({
            name: window.get_title(),
            icon: icon,
            // If selected, we switch to the corresponding window. If window peeking is
            // enabled, this is not required as the hover event was fired already.
            onSelect: () => {
              window.get_workspace().activate_with_focus(
                  window, global.display.get_current_time_roundtrip());
            },

            // If hovered, we switch to the corresponding window if window peeking is
            // enabled.
            onHover: () => {
              if (data.hoverPeeking) {
                wasMinimized = window.minimized;
                window.get_workspace().activate_with_focus(
                    window, global.display.get_current_time_roundtrip());
              }
            },

            onUnhover: () => {
              if (data.hoverPeeking && wasMinimized) {
                window.minimize();
              }
            }
          });
        });
      });

      return result;
    }
  };
}