/**
 * V-Shell (Vertical Workspaces)
 * layout.js
 *
 * @author     GdH <G-dH@github.com>
 * @copyright  2022 - 2024
 * @license    GPL-3.0
 *
 */

'use strict';

import GLib from 'gi://GLib';
import Meta from 'gi://Meta';
import Gio from 'gi://Gio';

import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';

let Me;
let opt;
let _timeouts;

export const LayoutModule = class {
    constructor(me) {
        Me = me;
        opt = Me.opt;
        _timeouts = {};

        this._firstActivation = true;
        this.moduleEnabled = false;
        this._overrides = null;
        this._originalUpdateHotCorners = null;
    }

    cleanGlobals() {
        Me = null;
        opt = null;
    }

    update(reset) {
        this._removeTimeouts();

        this.moduleEnabled = opt.get('layoutModule');
        const conflict = Me.Util.getEnabledExtensions('custom-hot-corners').length ||
                         Me.Util.getEnabledExtensions('dash-to-panel').length;

        if (conflict && !reset)
            console.warn(`[${Me.metadata.name}] Warning: "Layout" module disabled due to potential conflict with another extension`);

        reset = reset || !this.moduleEnabled || conflict;

        // don't touch the original code if module disabled
        if (reset && !this._firstActivation) {
            this._disableModule();
        } else if (!reset) {
            this._firstActivation = false;
            this._activateModule();
        }
        if (reset && this._firstActivation)
            console.debug('  LayoutModule - Keeping untouched');
    }

    _activateModule() {
        if (!this._overrides)
            this._overrides = new Me.Util.Overrides();

        _timeouts = {};

        this._overrides.addOverride('LayoutManager', Main.layoutManager, LayoutManagerCommon);
        this._overrides.addOverride('HotCorner', Layout.HotCorner.prototype, HotCornerCommon);

        Main.layoutManager._updatePanelBarrier();
        Main.layoutManager._updateHotCorners();

        if (!this._hotCornersEnabledConId) {
            this._interfaceSettings = new Gio.Settings({
                schema_id: 'org.gnome.desktop.interface',
            });
            this._hotCornersEnabledConId = this._interfaceSettings.connect('changed::enable-hot-corners',
                () => Main.layoutManager._updateHotCorners());
        }

        console.debug('  LayoutModule - Activated');
    }

    _disableModule() {
        if (this._overrides)
            this._overrides.removeAll();
        this._overrides = null;

        Main.layoutManager._updateHotCorners();

        if (this._hotCornersEnabledConId) {
            this._interfaceSettings.disconnect(this._hotCornersEnabledConId);
            this._hotCornersEnabledConId = 0;
            this._interfaceSettings = null;
        }

        console.debug('  LayoutModule - Disabled');
    }

    _removeTimeouts() {
        if (_timeouts) {
            Object.values(_timeouts).forEach(t => {
                if (t)
                    GLib.source_remove(t);
            });
            _timeouts = null;
        }
    }
};

const LayoutManagerCommon = {
    _updatePanelBarrier() {
        if (this._rightPanelBarrier) {
            this._rightPanelBarrier.destroy();
            this._rightPanelBarrier = null;
        }

        if (this._leftPanelBarrier) {
            this._leftPanelBarrier.destroy();
            this._leftPanelBarrier = null;
        }

        if (!this.primaryMonitor || !opt || Me.Util.getEnabledExtensions('hidetopbar'))
            return;

        if (this.panelBox.height) {
            const backend = !!Meta.Barrier.prototype.backend;
            let params = {};
            if (backend)
                params['backend'] = global.backend;
            else
                params['display'] = global.display;

            let primary = this.primaryMonitor;
            if ([0, 1, 3].includes(opt.HOT_CORNER_POSITION)) {
                params = Object.assign({}, params, {
                    x1: primary.x + primary.width, y1: this.panelBox.allocation.y1,
                    x2: primary.x + primary.width, y2: this.panelBox.allocation.y2,
                    directions: Meta.BarrierDirection.NEGATIVE_X,
                });
                this._rightPanelBarrier = new Meta.Barrier(params);
            }

            if ([2, 4].includes(opt.HOT_CORNER_POSITION)) {
                params = Object.assign({}, params, {
                    x1: primary.x, y1: this.panelBox.allocation.y1,
                    x2: primary.x, y2: this.panelBox.allocation.y2,
                    directions: Meta.BarrierDirection.POSITIVE_X,
                });
                this._leftPanelBarrier = new Meta.Barrier(params);
            }
        }
    },

    _updateHotCorners() {
        // avoid errors if called from foreign override
        if (!opt)
            return;

        // destroy old hot corners
        this.hotCorners.forEach(corner => corner?.destroy());
        this.hotCorners = [];

        if (!this._interfaceSettings.get_boolean('enable-hot-corners')) {
            this.emit('hot-corners-changed');
            return;
        }

        let size = this.panelBox.height ? this.panelBox.height : 27;

        // position 0 - default, 1-TL, 2-TR, 3-BL, 4-BR
        const position = opt.HOT_CORNER_POSITION;

        // build new hot corners
        for (let i = 0; i < this.monitors.length; i++) {
            let monitor = this.monitors[i];
            let cornerX, cornerY;

            if (position === 0) {
                cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
                cornerY = monitor.y;
            } else if (position === 1) {
                cornerX = monitor.x;
                cornerY = monitor.y;
            } else if (position === 2) {
                cornerX = monitor.x + monitor.width;
                cornerY = monitor.y;
            } else if (position === 3) {
                cornerX = monitor.x;
                cornerY = monitor.y + monitor.height;
            } else {
                cornerX = monitor.x + monitor.width;
                cornerY = monitor.y + monitor.height;
            }

            let haveCorner = true;

            if (i !== this.primaryIndex) {
                // Check if we have a top left (right for RTL) corner.
                // I.e. if there is no monitor directly above or to the left(right)
                let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
                let besideY = cornerY;
                let aboveX = cornerX;
                let aboveY = cornerY - 1;

                for (let j = 0; j < this.monitors.length; j++) {
                    if (i === j)
                        continue;
                    let otherMonitor = this.monitors[j];
                    if (besideX >= otherMonitor.x &&
                        besideX < otherMonitor.x + otherMonitor.width &&
                        besideY >= otherMonitor.y &&
                        besideY < otherMonitor.y + otherMonitor.height) {
                        haveCorner = false;
                        break;
                    }
                    if (aboveX >= otherMonitor.x &&
                        aboveX < otherMonitor.x + otherMonitor.width &&
                        aboveY >= otherMonitor.y &&
                        aboveY < otherMonitor.y + otherMonitor.height) {
                        haveCorner = false;
                        break;
                    }
                }
            }

            if (haveCorner) {
                let corner = new Layout.HotCorner(this, monitor, cornerX, cornerY);
                corner.setBarrierSize(size, false);
                this.hotCorners.push(corner);
            } else {
                this.hotCorners.push(null);
            }
        }

        this.emit('hot-corners-changed');
    },
};

const HotCornerCommon = {
    after__init() {
        let angle = 0;
        switch (opt.HOT_CORNER_POSITION) {
        case 2:
            angle = 90;
            break;
        case 3:
            angle = 270;
            break;
        case 4:
            angle = 180;
            break;
        }

        this._ripples._ripple1.rotation_angle_z = angle;
        this._ripples._ripple2.rotation_angle_z = angle;
        this._ripples._ripple3.rotation_angle_z = angle;
    },

    setBarrierSize(size, notMyCall = true) {
        // ignore calls from the original _updateHotCorners() callback to avoid building barriers outside screen
        if (notMyCall && size > 0)
            return;

        if (this._verticalBarrier) {
            this._pressureBarrier.removeBarrier(this._verticalBarrier);
            this._verticalBarrier.destroy();
            this._verticalBarrier = null;
        }

        if (this._horizontalBarrier) {
            this._pressureBarrier.removeBarrier(this._horizontalBarrier);
            this._horizontalBarrier.destroy();
            this._horizontalBarrier = null;
        }

        if (size > 0) {
            const primaryMonitor = global.display.get_primary_monitor();
            const monitor = this._monitor;
            const extendV = opt && opt.HOT_CORNER_ACTION && opt.HOT_CORNER_EDGE && opt.DASH_VERTICAL && monitor.index === primaryMonitor;
            const extendH = opt && opt.HOT_CORNER_ACTION && opt.HOT_CORNER_EDGE && !opt.DASH_VERTICAL && monitor.index === primaryMonitor;

            const backend = !!Meta.Barrier.prototype.backend;
            let params = {};
            if (backend)
                params['backend'] = global.backend;
            else
                params['display'] = global.display;

            if (opt.HOT_CORNER_POSITION <= 1) {
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x,
                    y1: this._y, y2: this._y + (extendV ? monitor.height : size),
                    directions: Meta.BarrierDirection.POSITIVE_X,
                });
                this._verticalBarrier = new Meta.Barrier(params);
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x + (extendH ? monitor.width : size),
                    y1: this._y, y2: this._y,
                    directions: Meta.BarrierDirection.POSITIVE_Y,
                });
                this._horizontalBarrier = new Meta.Barrier(params);
            } else if (opt.HOT_CORNER_POSITION === 2) {
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x,
                    y1: this._y, y2: this._y + (extendV ? monitor.height : size),
                    directions: Meta.BarrierDirection.NEGATIVE_X,
                });
                this._verticalBarrier = new Meta.Barrier(params);
                params = Object.assign({}, params, {
                    x1: this._x - size, x2: this._x,
                    y1: this._y, y2: this._y,
                    directions: Meta.BarrierDirection.POSITIVE_Y,
                });
                this._horizontalBarrier = new Meta.Barrier(params);
            } else if (opt.HOT_CORNER_POSITION === 3) {
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x,
                    y1: this._y, y2: this._y - size,
                    directions: Meta.BarrierDirection.POSITIVE_X,
                });
                this._verticalBarrier = new Meta.Barrier(params);
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x + (extendH ? monitor.width : size),
                    y1: this._y, y2: this._y,
                    directions: Meta.BarrierDirection.NEGATIVE_Y,
                });
                this._horizontalBarrier = new Meta.Barrier(params);
            } else if (opt.HOT_CORNER_POSITION === 4) {
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x,
                    y1: this._y, y2: this._y - size,
                    directions: Meta.BarrierDirection.NEGATIVE_X,
                });
                this._verticalBarrier = new Meta.Barrier(params);
                params = Object.assign({}, params, {
                    x1: this._x, x2: this._x - size,
                    y1: this._y, y2: this._y,
                    directions: Meta.BarrierDirection.NEGATIVE_Y,
                });
                this._horizontalBarrier = new Meta.Barrier(params);
            }

            this._pressureBarrier.addBarrier(this._verticalBarrier);
            this._pressureBarrier.addBarrier(this._horizontalBarrier);
        }
    },

    _toggleOverview() {
        if (!opt.HOT_CORNER_ACTION || (!opt.HOT_CORNER_FULLSCREEN && this._monitor.inFullscreen && !Main.overview.visible))
            return;

        if (Main.overview.shouldToggleByCornerOrButton()) {
            if (Main.overview._shown) {
                this._toggleWindowPicker(true);
            } else if ((opt.HOT_CORNER_ACTION === 2 && !Me.Util.isCtrlPressed()) || ([3, 4, 5, 6].includes(opt.HOT_CORNER_ACTION) && Me.Util.isCtrlPressed())) {
                // Default overview
                opt.OVERVIEW_MODE = 0;
                opt.OVERVIEW_MODE2 = false;
                opt.WORKSPACE_MODE = 1;
                this._toggleWindowPicker(true, true);
            } else if (opt.HOT_CORNER_ACTION === 1) {
                Main.overview.resetOverviewMode();
                this._toggleWindowPicker(true, true);
            } else if ((opt.HOT_CORNER_ACTION === 3 && !Me.Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 2 && Me.Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 6 && Me.Util.isCtrlPressed())) {
                // Applications
                this._toggleApplications(true);
            } else if (opt.HOT_CORNER_ACTION === 4 && !Me.Util.isCtrlPressed()) {
                // Overview - static ws preview
                opt.OVERVIEW_MODE = 1;
                opt.OVERVIEW_MODE2 = false;
                opt.WORKSPACE_MODE = 0;
                this._toggleWindowPicker(true, true);
            } else if (opt.HOT_CORNER_ACTION === 5 && !Me.Util.isCtrlPressed()) {
                // Overview - static ws
                opt.OVERVIEW_MODE = 2;
                opt.OVERVIEW_MODE2 = true;
                opt.WORKSPACE_MODE = 0;
                this._toggleWindowPicker(true, true);
            } else if (opt.HOT_CORNER_ACTION === 6 && !Me.Util.isCtrlPressed()) {
                // Window search provider
                opt.OVERVIEW_MODE = 2;
                opt.OVERVIEW_MODE2 = true;
                opt.WORKSPACE_MODE = 0;
                this._toggleWindowSearchProvider();
            }
            if (opt.HOT_CORNER_RIPPLES && Main.overview.animationInProgress)
                this._ripples.playAnimation(this._x, this._y);
        }
    },

    _toggleWindowPicker(leaveOverview = false, customOverviewMode = false) {
        if (Main.overview._shown && (leaveOverview || !Main.overview.dash.showAppsButton.checked)) {
            Main.overview.hide();
        } else if (Main.overview.dash.showAppsButton.checked) {
            Main.overview.dash.showAppsButton.checked = false;
        } else {
            const focusWindow = global.display.get_focus_window();
            // at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard
            if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) {
                // following should help when windowed VBox Machine has focus.
                global.stage.set_key_focus(Main.panel);
                // key focus doesn't take the effect immediately, we must wait for it
                // still looking for better solution!
                _timeouts.releaseKeyboardTimeoutId = GLib.timeout_add(
                    GLib.PRIORITY_DEFAULT,
                    // delay cannot be too short
                    200,
                    () => {
                        Main.overview.show(1, customOverviewMode);

                        _timeouts.releaseKeyboardTimeoutId = 0;
                        return GLib.SOURCE_REMOVE;
                    }
                );
            } else {
                Main.overview.show(1, customOverviewMode);
            }
        }
    },

    _toggleApplications(leaveOverview = false) {
        if ((leaveOverview && Main.overview._shown) || Main.overview.dash.showAppsButton.checked) {
            Main.overview.hide();
        } else {
            const focusWindow = global.display.get_focus_window();
            // at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard
            if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) {
                // following should help when windowed VBox Machine has focus.
                global.stage.set_key_focus(Main.panel);
                // key focus doesn't take the effect immediately, we must wait for it
                // still looking for better solution!
                _timeouts.releaseKeyboardTimeoutId = GLib.timeout_add(
                    GLib.PRIORITY_DEFAULT,
                    // delay cannot be too short
                    200,
                    () => {
                        Main.overview.show(2);

                        _timeouts.releaseKeyboardTimeoutId = 0;
                        return GLib.SOURCE_REMOVE;
                    }
                );
            } else if (Main.overview._shown) {
                Main.overview.dash.showAppsButton.checked = true;
            } else {
                Main.overview.show(2); // 2 for App Grid
            }
        }
    },

    _toggleWindowSearchProvider() {
        if (!Main.overview.searchController._searchActive) {
            opt.OVERVIEW_MODE = 2;
            opt.OVERVIEW_MODE2 = true;
            opt.WORKSPACE_MODE = 0;
            this._toggleWindowPicker(false, true);
            const prefix = Me.WSP_PREFIX;
            const position = prefix.length;
            const searchEntry = Main.overview.searchEntry;
            searchEntry.set_text(prefix);
            // searchEntry.grab_key_focus();
            searchEntry.get_first_child().set_cursor_position(position);
            searchEntry.get_first_child().set_selection(position, position);
        } else {
            // Main.overview.searchEntry.text = '';
            Main.overview.hide();
        }
    },
};
