/**
 * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact schukai GmbH.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import {
	assembleMethodSymbol,
	CustomElement,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import "../notify/notify.mjs";
import { fireCustomEvent } from "../../dom/events.mjs";
import { Observer } from "../../types/observer.mjs";
import { SplitPanelStyleSheet } from "./stylesheet/split-panel.mjs";
import { instanceSymbol } from "../../constants.mjs";
import { internalSymbol } from "../../constants.mjs";

export { SplitPanel, TYPE_VERTICAL, TYPE_HORIZONTAL };

/**
 * @private
 * @type {symbol}
 */
const splitScreenElementSymbol = Symbol("splitScreenElement");

/**
 * @private
 * @type {symbol}
 */
const draggerElementSymbol = Symbol("draggerElement");
/**
 * @private
 * @type {symbol}
 */
const startPanelElementSymbol = Symbol("startPanelElement");
/**
 * @private
 * @type {symbol}
 */
const endPanelElementSymbol = Symbol("endPanelElement");
/**
 * @private
 * @type {symbol}
 */
const handleElementSymbol = Symbol("handleElement");

/**
 *
 * @type {string}
 */
const TYPE_VERTICAL = "vertical";
/**
 *
 * @type {string}
 */
const TYPE_HORIZONTAL = "horizontal";

/**
 * A SplitPanel Control
 *
 * @fragments /fragments/components/layout/split-panel/
 *
 * @example /examples/components/layout/split-panel-simple
 *
 * @since 3.54.0
 * @copyright schukai GmbH
 * @summary The SplitPanel control is a simple layout control that allows you to split the screen
 * into two parts. The split can be either vertical or horizontal. The control provides a
 * draggable handle that allows you to adjust the size of the two panels.
 */
class SplitPanel extends CustomElement {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/components/layout/split-panel");
	}

	/**
	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
	 *
	 * The individual configuration values can be found in the table.
	 *
	 * @property {Object} templates Template definitions
	 * @property {string} templates.main Main template
	 * @property {string} splitType Split type (vertical or horizontal)
	 * @property {string} dimension Dimension
	 * @property {string} dimension.initial Initial dimension of the start panel
	 * @property {string} dimension.max Maximum dimension of the start panel (in percentage)
	 * @property {string} dimension.min Minimum dimension of the start panel (in percentage)
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			splitType: TYPE_VERTICAL,
			dimension: {
				initial: "60%",
				max: "80%",
				min: "20%",
			},
		});
	}

	fullStartScreen() {
		this.setDimension("100%");
		return this;
	}

	fullEndScreen() {
		this.setDimension("0%");
		return this;
	}

	isFullStartScreen() {
		return this[internalSymbol].getSubject().currentDimension === "100%";
	}

	isFullEndScreen() {
		return this[internalSymbol].getSubject().currentDimension === "0%";
	}

	isInitialScreen() {
		return (
			this[internalSymbol].getSubject().currentDimension ===
			this.getOption("dimension").initial
		);
	}

	resetScreen() {
		this.setDimension(this.getOption("dimension").initial);
		return this;
	}

	setContent(html) {
		this.setOption("content", html);
		return this;
	}

	/**
	 *
	 * @return {Monster.Components.Host.Viewer}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();

		initControlReferences.call(this);
		initEventHandler.call(this);
		//applyPanelDimensions.call(this);
		this.setDimension(this.getOption("dimension").initial);
	}

	/**
	 * Check if the dimension is a percentage and within a valid range, then set the dimension option.
	 *
	 * @param {string} dimension - The dimension to be set, can be in percentage or absolute value.
	 * @return {Object} - Returns the current object instance for chaining.
	 */
	setDimension(dimension) {
		// check if percent and greater than100
		if (dimension.includes("%")) {
			if (parseInt(dimension) > 100) {
				throw new Error("dimension must be less than 100%");
			} else if (parseInt(dimension) < 0) {
				throw new Error("dimension must be greater than 0%");
			}
		}

		this[internalSymbol].getSubject().currentDimension = dimension;
		return this;
	}

	/**
	 *
	 * @return {string}
	 */
	static getTag() {
		return "monster-split-panel";
	}

	/**
	 * @return {CSSStyleSheet[]}
	 */
	static getCSSStyleSheet() {
		return [SplitPanelStyleSheet];
	}
}

/**
 * Set the dimensions of the panel based on the split type.
 * @fires monster-dimension-changed
 */
function applyPanelDimensions() {
	const splitType = this.getOption("splitType");
	const dimension = this[internalSymbol].getSubject().currentDimension;

	if (splitType === TYPE_VERTICAL) {
		this[startPanelElementSymbol].style.width = dimension;
		this[endPanelElementSymbol].style.width = `calc(100% - ${dimension} - 5px)`;
		this[draggerElementSymbol].style.cursor = "ew-resize";
		this[splitScreenElementSymbol].classList.add("vertical");
		this[splitScreenElementSymbol].classList.remove("horizontal");
	} else {
		this[startPanelElementSymbol].style.height = dimension;
		this[endPanelElementSymbol].style.height =
			`calc(100% - ${dimension} - 5px)`;
		this[draggerElementSymbol].style.cursor = "ns-resize";
		this[splitScreenElementSymbol].classList.add("horizontal");
		this[splitScreenElementSymbol].classList.remove("vertical");
	}

	fireCustomEvent(this, "monster-dimension-changed", {
		controller: this,
		dimension: dimension,
	});
}

/**
 * @private
 * @return {Select}
 * @throws {Error} no shadow-root is defined
 */
function initControlReferences() {
	if (!this.shadowRoot) {
		throw new Error("no shadow-root is defined");
	}

	this[splitScreenElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=split-panel]",
	);
	this[draggerElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=dragger]",
	);
	this[handleElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=handle]",
	);

	this[startPanelElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=startPanel]",
	);
	this[endPanelElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=endPanel]",
	);
}

/**
 * @private
 */
function initEventHandler() {
	const self = this;

	let lastDimension = this[internalSymbol].getSubject().currentDimension;
	let lastType = this.getOption("splitType");

	this[internalSymbol].getSubject().isDragging = false;

	// @todo: add better touch support
	const eventTypes = ["dblclick", "touchstart"];
	for (const eventType of eventTypes) {
		this[draggerElementSymbol].addEventListener(eventType, () => {
			self[internalSymbol].getSubject().isDragging = false;
			lastDimension = undefined;

			let currentDimension;
			if (self.getOption("splitType") === TYPE_VERTICAL) {
				const topPanel = self[startPanelElementSymbol];
				currentDimension = topPanel.style.width;
			} else {
				const topPanel = self[startPanelElementSymbol];
				currentDimension = topPanel.style.height;
			}

			if (currentDimension === self.getOption("dimension").initial) {
				self.setDimension(self.getOption("dimension").max);
			} else if (currentDimension === self.getOption("dimension").max) {
				self.setDimension(self.getOption("dimension").min);
			} else if (currentDimension === self.getOption("dimension").min) {
				self.setDimension(self.getOption("dimension").initial);
			} else {
				self.setDimension(self.getOption("dimension").initial);
			}
		});
	}

	this[draggerElementSymbol].addEventListener("mousedown", () => {
		self[internalSymbol].getSubject().isDragging = true;

		const eventListener = (e) => {
			e.preventDefault();

			// the 5px are wrong and must be calc from css property --monster-dragger-width

			let draggerWidth = getComputedStyle(
				self[draggerElementSymbol],
			).getPropertyValue("--monster-dragger-width");
			if (
				draggerWidth === "" ||
				draggerWidth === undefined ||
				draggerWidth === null
			) {
				draggerWidth = "0";
			}

			if (!self[internalSymbol].getSubject().isDragging) {
				return;
			}

			if (self.getOption("splitType") === TYPE_HORIZONTAL) {
				const containerOffsetTop = self[splitScreenElementSymbol].offsetTop;
				const topPanel = self[startPanelElementSymbol];
				const bottomPanel = self[endPanelElementSymbol];
				let newTopHeight = e.clientY - containerOffsetTop;

				const min = this.getOption("dimension").min;
				const max = this.getOption("dimension").max;

				const topAsPercent =
					(newTopHeight / this[splitScreenElementSymbol].offsetHeight) * 100;
				if (parseInt(min) > topAsPercent) {
					newTopHeight = min;
				} else if (parseInt(max) < topAsPercent) {
					newTopHeight = max;
				} else {
					newTopHeight = topAsPercent + "%";
				}

				// calc new top height to pixel
				const newTopHeightPx =
					(parseInt(newTopHeight) / 100) *
					this[splitScreenElementSymbol].offsetHeight;

				topPanel.style.height = `${newTopHeightPx}px`;
				bottomPanel.style.height = `calc(100% - ${newTopHeightPx}px - ${draggerWidth})`; // 5px is dragger height
			} else {
				const containerOffsetLeft = self[splitScreenElementSymbol].offsetLeft;
				const leftPanel = self[startPanelElementSymbol];
				const rightPanel = self[endPanelElementSymbol];
				let newLeftWidth = e.clientX - containerOffsetLeft;

				const min = this.getOption("dimension").min;
				const max = this.getOption("dimension").max;

				const leftAsPercent =
					(newLeftWidth / this[splitScreenElementSymbol].offsetWidth) * 100;

				if (parseInt(min) > leftAsPercent) {
					newLeftWidth = min;
				} else if (parseInt(max) < leftAsPercent) {
					newLeftWidth = max;
				} else {
					newLeftWidth = leftAsPercent + "%";
				}

				leftPanel.style.width = `${newLeftWidth}`;
				rightPanel.style.width = `calc(100% - ${newLeftWidth} - ${draggerWidth})`; // 5px is dragger width
			}
		};

		const dragEventHandler = (e) => {
			self[internalSymbol].getSubject().isDragging = false;
			document.removeEventListener("mousemove", eventListener);
			document.removeEventListener("mouseup", eventListener);
		};

		document.addEventListener("mousemove", eventListener);
		document.addEventListener("mouseup", dragEventHandler);
	});

	this[internalSymbol].attachObserver(
		new Observer(() => {
			let apply = false;

			if (
				lastDimension !== this[internalSymbol].getSubject().currentDimension
			) {
				lastDimension = this[internalSymbol].getSubject().currentDimension;
				apply = true;
			}

			if (lastType !== this.getOption("splitType")) {
				lastType = this.getOption("splitType");
				apply = true;
			}

			if (apply) {
				applyPanelDimensions.call(this);
			}
		}),
	);

	return this;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="split-panel" part="control">
            <div data-monster-role="startPanel" class="panel" part="startPanel">
                <slot name="start"></slot>
            </div>
            <div data-monster-role="dragger" part="dragger">
                <div data-monster-role="handle" part="handle"></div>
            </div>
            <div data-monster-role="endPanel" class="panel" part="endPanel">
                <slot name="end"></slot>
            </div>
        </div>`;
}

registerCustomElement(SplitPanel);