import Atk from 'gi://Atk';
import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
import St from 'gi://St';

import * as Main from './main.js';
import * as PopupMenu from './popupMenu.js';

export const ButtonBox = GObject.registerClass(
class ButtonBox extends St.Widget {
    _init(params) {
        super._init({
            style_class: 'panel-button',
            x_expand: true,
            y_expand: true,
            ...params,
        });

        this._delegate = this;

        this.container = new St.Bin({child: this});

        this.connect('style-changed', this._onStyleChanged.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));

        this._minHPadding = this._natHPadding = 0.0;
    }

    _onStyleChanged(actor) {
        const themeNode = actor.get_theme_node();

        this._minHPadding = themeNode.get_length('-minimum-hpadding');
        this._natHPadding = themeNode.get_length('-natural-hpadding');
    }

    vfunc_get_preferred_width(_forHeight) {
        const child = this.get_first_child();
        let minimumSize, naturalSize;

        if (child)
            [minimumSize, naturalSize] = child.get_preferred_width(-1);
        else
            minimumSize = naturalSize = 0;

        minimumSize += 2 * this._minHPadding;
        naturalSize += 2 * this._natHPadding;

        return [minimumSize, naturalSize];
    }

    vfunc_get_preferred_height(_forWidth) {
        const child = this.get_first_child();

        if (child)
            return child.get_preferred_height(-1);

        return [0, 0];
    }

    vfunc_allocate(box) {
        this.set_allocation(box);

        const child = this.get_first_child();
        if (!child)
            return;

        const [, natWidth] = child.get_preferred_width(-1);

        const availWidth = box.x2 - box.x1;
        const availHeight = box.y2 - box.y1;

        const childBox = new Clutter.ActorBox();
        if (natWidth + 2 * this._natHPadding <= availWidth) {
            childBox.x1 = this._natHPadding;
            childBox.x2 = availWidth - this._natHPadding;
        } else {
            childBox.x1 = this._minHPadding;
            childBox.x2 = availWidth - this._minHPadding;
        }

        childBox.y1 = 0;
        childBox.y2 = availHeight;

        child.allocate(childBox);
    }

    _onDestroy() {
        this.container.child = null;
        this.container.destroy();
    }
});

export const Button = GObject.registerClass({
    Signals: {'menu-set': {}},
}, class PanelMenuButton extends ButtonBox {
    _init(menuAlignment, nameText, dontCreateMenu) {
        super._init({
            reactive: true,
            can_focus: true,
            track_hover: true,
            accessible_name: nameText ?? '',
            accessible_role: Atk.Role.MENU,
        });

        this._clickGesture = new Clutter.ClickGesture();
        this._clickGesture.set_recognize_on_press(true);
        this._clickGesture.connect('recognize', () => {
            this.menu?.toggle();
        });
        this._clickGesture.set_enabled(!dontCreateMenu);
        this.add_action(this._clickGesture);

        if (dontCreateMenu)
            this.menu = new PopupMenu.PopupDummyMenu(this);
        else
            this.setMenu(new PopupMenu.PopupMenu(this, menuAlignment, St.Side.TOP));

        this.connect('key-press-event',
            (o, ev) => global.focus_manager.navigate_from_event(ev));
    }

    setSensitive(sensitive) {
        this.reactive = sensitive;
        this.can_focus = sensitive;
        this.track_hover = sensitive;
    }

    setMenu(menu) {
        if (this.menu)
            this.menu.destroy();

        this.menu = menu;
        if (this.menu) {
            this.menu.actor.add_style_class_name('panel-menu');
            this.menu.connect('open-state-changed', this._onOpenStateChanged.bind(this));
            this.menu.actor.connect('key-press-event', this._onMenuKeyPress.bind(this));

            Main.uiGroup.add_child(this.menu.actor);
            this.menu.actor.hide();
        }
        this._clickGesture.set_enabled(this.menu !== null);
        this.emit('menu-set');
    }

    vfunc_hide() {
        super.vfunc_hide();

        if (this.menu)
            this.menu.close();
    }

    _onMenuKeyPress(actor, event) {
        if (global.focus_manager.navigate_from_event(event))
            return Clutter.EVENT_STOP;

        const symbol = event.get_key_symbol();
        if (symbol === Clutter.KEY_Left || symbol === Clutter.KEY_Right) {
            const group = global.focus_manager.get_group(this);
            if (group) {
                const direction = symbol === Clutter.KEY_Left ? St.DirectionType.LEFT : St.DirectionType.RIGHT;
                group.navigate_focus(this, direction, false);
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onOpenStateChanged(menu, open) {
        if (open)
            this.add_style_pseudo_class('active');
        else
            this.remove_style_pseudo_class('active');

        // Setting the max-height won't do any good if the minimum height of the
        // menu is higher then the screen; it's useful if part of the menu is
        // scrollable so the minimum height is smaller than the natural height
        const workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
        const scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        const verticalMargins = this.menu.actor.margin_top + this.menu.actor.margin_bottom;

        // The workarea and margin dimensions are in physical pixels, but CSS
        // measures are in logical pixels, so make sure to consider the scale
        // factor when computing max-height
        const maxHeight = Math.round((workArea.height - verticalMargins) / scaleFactor);
        this.menu.actor.style = `max-height: ${maxHeight}px;`;
    }

    _onDestroy() {
        if (this.menu)
            this.menu.destroy();
        super._onDestroy();
    }
});
