/**
 * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
 * Copyright 2025 by Keenthemes Inc
 */

declare global {
	interface Window {
		KTGlobalComponentsConfig: object;
	}
}

import KTData from '../helpers/data';
import KTDom from '../helpers/dom';
import KTUtils from '../helpers/utils';
import { KTOptionType } from '../types';

export default class KTComponent {
	protected _dataOptionPrefix: string = 'kt-';
	protected _name: string;
	protected _defaultConfig: object;
	protected _config: object;
	protected _events: Map<string, Map<string, CallableFunction>> = new Map();
	protected _uid: string | null = null;
	protected _element: HTMLElement | null = null;

	/**
	 * Check if component should skip initialization
	 * Returns true if element already has an instance and is still connected to DOM
	 * Returns false if element should be initialized (no instance or disconnected)
	 * @param element The element to check
	 * @returns true if initialization should be skipped, false otherwise
	 */
	protected _shouldSkipInit(element: HTMLElement): boolean {
		if (!KTData.has(element, this._name)) {
			return false;
		}

		const existingInstance = KTData.get(element, this._name) as KTComponent;

		// If element is not connected to DOM, dispose old instance and allow reinitialization
		if (element.isConnected === false) {
			if (existingInstance && typeof existingInstance.dispose === 'function') {
				existingInstance.dispose();
			}
			return false;
		}

		// Element is connected and has instance, skip initialization
		return true;
	}

	protected _init(element: HTMLElement | null) {
		element = KTDom.getElement(element);

		if (!element) {
			return;
		}

		this._element = element;
		this._events = new Map();
		this._uid = KTUtils.geUID(this._name);

		this._element.setAttribute(`data-kt-${this._name}-initialized`, 'true');

		KTData.set(this._element, this._name, this);
	}

	protected async _fireEvent(
		eventType: string,
		payload: object | null = null,
	): Promise<void> {
		const callbacks = this._events.get(eventType);

		if (callbacks instanceof Map == false) {
			return;
		}

		await Promise.all(
			Array.from(callbacks.values())
				.filter((callable) => {
					return typeof callable === 'function';
				})
				.map((callable) => {
					return Promise.resolve(callable(payload));
				}),
		);
	}

	protected _dispatchEvent(
		eventType: string,
		payload: object | null = null,
	): void {
		const event = new CustomEvent(eventType, {
			detail: { payload },
			bubbles: true,
			cancelable: true,
			composed: false,
		});

		if (!this._element) return;
		this._element.dispatchEvent(event);
	}

	protected _getOption(name: string): KTOptionType {
		const value = this._config[name as keyof object];
		if (!this._element) {
			return value as KTOptionType;
		}
		const reponsiveValue = KTDom.getCssProp(
			this._element,
			`--kt-${this._name}-${KTUtils.camelReverseCase(name)}`,
		);

		return reponsiveValue || value;
	}

	protected _getGlobalConfig(): object {
		if (
			window.KTGlobalComponentsConfig &&
			(window.KTGlobalComponentsConfig as object)[this._name as keyof object]
		) {
			return (window.KTGlobalComponentsConfig as object)[
				this._name as keyof object
			] as object;
		} else {
			return {};
		}
	}

	protected _buildConfig(config: object = {}): void {
		if (!this._element) return;

		this._config = {
			...this._defaultConfig,
			...this._getGlobalConfig(),
			...KTDom.getDataAttributes(
				this._element,
				this._dataOptionPrefix + this._name,
			),
			...config,
		};
	}

	/**
	 * Merge config into the existing _config in place. Use when re-applying config to an
	 * already-initialized instance so handlers that hold a reference to _config see updates.
	 */
	protected _mergeConfig(config: object): void {
		if (
			config &&
			typeof config === 'object' &&
			Object.keys(config).length > 0
		) {
			Object.assign(this._config, config);
		}
	}

	public dispose(): void {
		if (!this._element) return;

		this._element.removeAttribute(`data-kt-${this._name}-initialized`);
		KTData.remove(this._element, this._name);
	}

	public on(eventType: string, callback: CallableFunction): string {
		const eventId = KTUtils.geUID();

		if (!this._events.get(eventType)) {
			this._events.set(eventType, new Map());
		}

		const eventMap = this._events.get(eventType);
		if (!eventMap) {
			return eventId;
		}
		eventMap.set(eventId, callback);

		return eventId;
	}

	public off(eventType: string, eventId: string): void {
		this._events.get(eventType)?.delete(eventId);
	}

	public getOption(name: string): KTOptionType {
		return this._getOption(name as keyof object);
	}

	public getElement(): HTMLElement | null {
		if (!this._element) return null;
		return this._element;
	}
}
