import Clutter from 'gi://Clutter';
import Cogl from 'gi://Cogl';
import GObject from 'gi://GObject';
import Graphene from 'gi://Graphene';
import Shell from 'gi://Shell';
import St from 'gi://St';

import { ActiveState } from '../../common/constants.js';
import { flagsParamSpec, registerClass } from '../../common/gjs.js';
import { ClipboardEntry } from '../../database/database.js';
import { Shortcut } from '../../misc/shortcuts.js';
import { ClipboardItemHeader } from './clipboardItemHeader.js';

var __decorate =
	(this && this.__decorate) ||
	function (decorators, target, key, desc) {
		var c = arguments.length,
			r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,
			d;
		if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
			r = Reflect.decorate(decorators, target, key, desc);
		else
			for (var i = decorators.length - 1; i >= 0; i--)
				if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
		return (c > 3 && r && Object.defineProperty(target, key, r), r);
	};

export const MiddleClickAction = {
	None: 0,
	Pin: 1,
	Delete: 2,
};

let ClipboardItem = class ClipboardItem extends St.Button {
	ext;
	entry;
	_protectPinned = true;
	_protectTagged = true;
	_middleClickAction = MiddleClickAction.None;
	_box;
	_header;
	_content;

	constructor(ext, entry, icon, title) {
		super({
			style_class: 'clipboard-item',
			button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
			x_expand: false,
			y_expand: false,
			track_hover: true,
			can_focus: true,
			width: 300,
			height: 200,
		});
		this.ext = ext;
		this.entry = entry;
		this._box = new St.Widget({
			style_class: 'clipboard-item-box',
			x_expand: true,
			y_expand: true,
			layout_manager: new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL }),
		});
		this.set_child(this._box);
		this._header = new ClipboardItemHeader(ext, icon, title);
		this._box.add_child(this._header);
		this._content = new St.BoxLayout({
			style_class: 'clipboard-item-content',
			orientation: Clutter.Orientation.VERTICAL,
			x_expand: true,
			y_expand: true,
			clip_to_allocation: true,
			effect: new HoleEffect(this._header.buttons),
		});
		this._box.add_child(this._content);

		// Bind properties
		entry.bind_property(
			'pinned',
			this._header,
			'pinned',
			GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE,
		);
		entry.bind_property('datetime', this._header, 'datetime', GObject.BindingFlags.SYNC_CREATE);
		entry.bind_property('tag', this._header, 'tag', GObject.BindingFlags.SYNC_CREATE);
		entry.bind_property(
			'title',
			this._header,
			'custom-title',
			GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE,
		);

		// prettier-ignore
		this.ext.settings.connectObject('changed::item-width', this.updateSize.bind(this), 'changed::item-height', this.updateSize.bind(this), 'changed::dynamic-item-height', this.updateSize.bind(this), 'changed::clipboard-orientation', this.updateSize.bind(this), 'changed::protect-pinned', this.updateProtection.bind(this), 'changed::protect-tagged', this.updateProtection.bind(this), 'changed::middle-click-action', this.updateMiddleClickAction.bind(this), 'changed::show-header', this.updateHeader.bind(this), 'changed::show-item-title', this.updateHeader.bind(this), 'changed::header-controls-visibility', this.updateHeader.bind(this), this);
		this.bind_property('active', this._header, 'active', GObject.BindingFlags.SYNC_CREATE);
		this.ext.shortcutsManager?.bind_property(
			'shift',
			this._header,
			'force-delete',
			GObject.BindingFlags.SYNC_CREATE,
		);
		this.updateSize();
		this.updateProtection();
		this.updateMiddleClickAction();
		this.updateHeader();

		// Connect signals
		this.connect('notify::hover', () => this.notify('active'));
		this.connect('style-changed', () => this.notify('active'));
		this._header.connect('delete', () => this.forceDelete());
		this._header.connect('open-menu', (_, x, y, w, h) => this.emit('open-menu', x, y, w, h));
		this._header.connect('editing-finished', () => this.grab_key_focus());
	}

	get active() {
		this.sync_hover();
		const focus = +this.has_key_focus() * ActiveState.Focus;
		const hover = +this.hover * ActiveState.Hover;
		const active = +(this.get_style_pseudo_class()?.includes('active') ?? false) * ActiveState.Active;
		return focus | hover | active;
	}

	search(query) {
		this.visible = query.matchesEntry(this.visible, this.entry, this.entry.content);
	}

	updateSize() {
		const width = this.ext.settings.get_int('item-width');
		const height = this.ext.settings.get_int('item-height');
		this.set_size(width, height);
		const dynamicHeight = this.ext.settings.get_boolean('dynamic-item-height');
		const orientation = this.ext.settings.get_enum('clipboard-orientation');
		if (dynamicHeight && orientation === Clutter.Orientation.VERTICAL) {
			this.set_height(-1);
			this.style = `max-height: ${height}px`;
		} else {
			this.style = '';
		}
	}

	updateProtection() {
		this._protectPinned = this.ext.settings.get_boolean('protect-pinned');
		this._protectTagged = this.ext.settings.get_boolean('protect-tagged');
		this._header.protectPinned = this._protectPinned;
		this._header.protectTagged = this._protectTagged;
	}

	updateMiddleClickAction() {
		this._middleClickAction = this.ext.settings.get_enum('middle-click-action');
		if (this._middleClickAction === MiddleClickAction.None) {
			this.button_mask &= ~St.ButtonMask.TWO;
		} else {
			this.button_mask |= St.ButtonMask.TWO;
		}
	}

	updateHeader() {
		const show = this.ext.settings.get_boolean('show-header');
		this._header.headerVisible = show;
		this._header.showTitle = this.ext.settings.get_boolean('show-item-title');
		this._header.controlsVisibility = this.ext.settings.get_enum('header-controls-visibility');
		if (show) {
			this.remove_style_class_name('no-header');
			this._box.set_child_above_sibling(this._content, this._header);
			this._box.layout_manager = new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL });
		} else {
			this.add_style_class_name('no-header');
			this._box.set_child_above_sibling(this._header, this._content);
			this._box.layout_manager = new Clutter.BinLayout();
		}
	}

	delete() {
		if (!(this._protectPinned && this.entry.pinned) && !(this._protectTagged && this.entry.tag)) {
			this.entry.emit('delete');
		}
	}

	forceDelete() {
		this.entry.emit('delete');
	}

	vfunc_clicked(clicked_button) {
		// Don't activate while editing the title
		if (this._header.isEditing) return;
		if (clicked_button === 1) {
			const event = Clutter.get_current_event();
			if (event.has_control_modifier()) {
				this.emit('activate-ctrl');
			} else if (event.has_shift_modifier()) {
				this.emit('activate-shift');
			} else {
				this.emit('activate');
			}
		} else if (clicked_button === 2) {
			if (this._middleClickAction === MiddleClickAction.Pin) {
				this.entry.pinned = !this.entry.pinned;
			} else if (this._middleClickAction === MiddleClickAction.Delete) {
				const event = Clutter.get_current_event();
				if (event.has_shift_modifier()) {
					this.forceDelete();
				} else {
					this.delete();
				}
			}
		} else if (clicked_button === 3) {
			const [x, y] = global.get_pointer();
			this.emit('open-menu', x, y, 0, 0);
			this.fake_release();
		}
	}

	vfunc_key_focus_in() {
		super.vfunc_key_focus_in();
		this.notify('active');
	}

	vfunc_key_focus_out() {
		super.vfunc_key_focus_out();
		this.notify('active');
	}

	vfunc_key_press_event(event) {
		const key = event.get_key_symbol();
		const action = this.ext.shortcutsManager?.getShortcutForKeyBinding(key, event.get_state());
		const shiftState = event.get_state() & ~Clutter.ModifierType.SHIFT_MASK;
		const shiftAction = this.ext.shortcutsManager?.getShortcutForKeyBinding(key, shiftState);

		// Pin
		if (action === Shortcut.Pin) {
			this.entry.pinned = !this.entry.pinned;
			return Clutter.EVENT_STOP;
		}

		// Delete
		if (action === Shortcut.Delete) {
			this.delete();
			return Clutter.EVENT_STOP;
		}

		// Force Delete
		if (shiftAction === Shortcut.Delete) {
			this.forceDelete();
			return Clutter.EVENT_STOP;
		}

		// Edit
		if (action === Shortcut.Edit) {
			this.emit('edit');
			return Clutter.EVENT_STOP;
		}

		// Edit Title
		if (action === Shortcut.EditTitle) {
			this._header.startEditing();
			return Clutter.EVENT_STOP;
		}

		// Menu
		if (action === Shortcut.Menu) {
			const [x, y] = this.get_transformed_position();
			this.emit('open-menu', x + 6, y, 0, 0);
			return Clutter.EVENT_STOP;
		}

		// Action
		const actionId = this.ext.shortcutsManager?.getActionForKeyBinding(key, event.get_state());
		if (actionId) this.emit('activate-action', actionId);
		return super.vfunc_key_press_event(event);
	}

	vfunc_get_preferred_height(for_width) {
		const theme = this.get_theme_node();
		const border = theme.get_border_width(St.Side.TOP) + theme.get_border_width(St.Side.BOTTOM);
		const maxHeight = theme.get_max_height();
		const [, nat] = this._box.get_preferred_height(for_width);
		const height = Math.min(nat + border, maxHeight);
		return [height, height];
	}

	destroy() {
		this.ext.settings.disconnectObject(this);
		super.destroy();
	}
};
ClipboardItem = __decorate(
	[
		registerClass({
			Properties: {
				entry: GObject.ParamSpec.object('entry', null, null, GObject.ParamFlags.READABLE, ClipboardEntry),
				active: flagsParamSpec('active', GObject.ParamFlags.READABLE, ActiveState, ActiveState.None),
			},
			Signals: {
				'activate': {},
				'activate-shift': {},
				'activate-ctrl': {},
				'activate-action': { param_types: [GObject.TYPE_STRING] },
				'edit': {},
				'open-menu': { param_types: [GObject.TYPE_INT, GObject.TYPE_INT, GObject.TYPE_INT, GObject.TYPE_INT] },
			},
		}),
	],
	ClipboardItem,
);

export { ClipboardItem };

// Based on https://gitlab.gnome.org/GNOME/mutter/-/blob/8b5c757bea75b7712bbe09c2018a8eb15b4d22cc/src/compositor/meta-background-content.c
let HoleEffect = class HoleEffect extends Shell.GLSLEffect {
	target;
	_sizeLocation;
	_holeBoxLocation;

	constructor(target) {
		super();
		this.target = target;
		this._sizeLocation = this.get_uniform_location('size');
		this._holeBoxLocation = this.get_uniform_location('hole_box');
		target.connect('notify::allocation', () => this.queue_repaint());
	}

	vfunc_paint_target(node, paintContext) {
		const size = this.actor.get_transformed_size();
		this.set_uniform_float(this._sizeLocation, 2, size);
		const position = this.target.apply_relative_transform_to_point(this.actor, new Graphene.Point3D());
		const [width, height] = this.target.get_transformed_size();
		this.set_uniform_float(this._holeBoxLocation, 4, [position.x - 1.5, position.y + 1, width + 2, height + 1]);
		super.vfunc_paint_target(node, paintContext);
	}

	vfunc_build_pipeline() {
		const dec = `
			uniform sampler2D tex;
			uniform vec2 size;
			uniform vec4 hole_box;

			float circle_bounds(vec2 p, vec2 center, float radius) {
				vec2 delta = p - center;
				float dist_squared = dot(delta, delta);
				float dist = sqrt(dist_squared);

				float outer_radius = radius + 0.5;
				if (dist >= outer_radius)
					return 1.0;

				float inner_radius = radius - 0.5;
				if (dist <= inner_radius)
					return 0.0;

				return dist - inner_radius;
			}

			float rounded_rect_coverage(vec2 p, vec4 bounds, float radius) {
				if (p.x < bounds.x || p.x > bounds.z || p.y < bounds.y || p.y > bounds.w)
					return 1.0;

				vec2 center;

				float center_left = bounds.x + radius;
				float center_right = bounds.z - radius;

				if (p.x < center_left)
					center.x = center_left;
				else if (p.x > center_right)
					center.x = center_right;
				else
					return 0.0;

				center.y = bounds.y + radius;

				return circle_bounds(p, center, radius);
			}`;
		const src = `
			vec2 uv = cogl_tex_coord_in[0].xy;
			vec2 p = size * cogl_tex_coord_in[0].xy;
			vec4 c = cogl_color_in * texture2D(tex, uv);

			if (hole_box.z <= 2.0 || hole_box.w <= 2.0) {
				cogl_color_out = c;
				return;
			}

			float radius = hole_box.w / 2.0;
			vec4 bounds = vec4(hole_box.x, hole_box.y, hole_box.x + hole_box.z, hole_box.y + hole_box.w);
			float alpha = rounded_rect_coverage(p, bounds, radius);
			cogl_color_out = vec4(c.rgb * alpha, min(alpha, c.a));`;
		this.add_glsl_snippet(Cogl.SnippetHook.FRAGMENT, dec, src, true);
	}
};
HoleEffect = __decorate([registerClass()], HoleEffect);
