/**
 * @license Copyright (c) 2017-2025, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md.
 */

import { BaseComponent } from '../components/base-component';
import { onHeaderBarStateChange } from './header-bar';

/**
 * Component for managing a header.
 */
export class Header extends BaseComponent {
	/**
	 * Creates a header component.
	 *
	 * @param element - The header bar DOM element
	 */
	constructor( element ) {
		super( {
			isOnOverlay: false,
			headerHeight: null
		} );

		this.elements = {
			header: element
		};
	}

	/**
	 * Gets the singleton instance of the Header
	 */
	static get the() {
		return this.instance;
	}

	/**
	 * Creates header component.
	 */
	static attach() {
		const headerElement = document.querySelector( '.js-header' );

		if ( !headerElement ) {
			return;
		}

		Header.instance = new Header( headerElement ).init();

		return Header.instance;
	}

	/**
	 * Returns the header element.
	 */
	get element() {
		return this.elements.header;
	}

	/**
	 * Initializes the header component.
	 * Sets the header height variable on body element.
	 */
	init() {
		this._trackHeight();

		window.addEventListener( 'scroll', this._trackHeight.bind( this ) );
		window.addEventListener( 'resize', this._trackHeight.bind( this ) );
		onHeaderBarStateChange( this._trackHeight.bind( this ) );

		this.render?.();

		return this;
	}

	/**
	 * Renders the component based on current state.
	 * Called automatically when state changes.
	 *
	 * @param prevState - Previous component state
	 * @param state - Current component state
	 */
	render( prevState, state = this.state ) {
		if ( !!prevState?.isOnOverlay !== !!state.isOnOverlay ) {
			this.element.classList.toggle( 'is-on-top-overlay', state.isOnOverlay );
			this.dispatchHeaderResizeEvent();
		} else if ( prevState?.height !== state.height && state.height !== null ) {
			this.dispatchHeaderResizeEvent();
		}
	}

	/**
	 * Toggles the header on overlay.
	 */
	toggleOnOverlay() {
		this.setState( {
			isOnOverlay: !this.state.isOnOverlay
		} );
	}

	/**
	 * Dispatches a custom event to notify other components about the header resize.
	 */
	dispatchHeaderResizeEvent( height = this.getVisibleHeight() ) {
		const headerResizeEvent = new CustomEvent( 'header:resize', {
			bubbles: true,
			detail: {
				height,
				text: () => 'Header size was changed.'
			}
		} );

		document.dispatchEvent( headerResizeEvent );
	}

	/**
	 * Calculates the visible height of the header.
	 */
	getVisibleHeight() {
		const rect = this.element.getBoundingClientRect();

		return Math.max( 0, Math.ceil( rect.bottom ) );
	}

	/**
	 * Gets the height of the header element.
	 */
	getHeight() {
		return this.element.offsetHeight;
	}

	/**
	 * Sets the header height variable on the body element.
	 */
	_trackHeight() {
		this.setState( {
			height: this.getVisibleHeight()
		} );
	}
}

/**
 * Creates a header spacer manager to handle showing/hiding the header
 * in overlay contexts.
 *
 * @param options - Options object
 * @param options.overlay - The overlay element
 * @param [options.setSpacing] - Optional callback to set custom spacing on the overlay
 * @returns An object with methods to show and hide the header spacer
 */

let _headerSpacerCount = 0;
let _headerSpacer = null;

export function createHeaderSpacerManager(
	{
		overlay,
		setSpacing
	}
) {
	const { element } = Header.the;
	const headerHeight = `${ element.offsetHeight }px`;

	// Default spacing function sets paddingTop
	const defaultSetSpacing = ( overlay, headerHeight ) => {
		overlay.style.paddingTop = headerHeight;
	};

	// Use provided spacing function or fall back to default
	const applySpacing = setSpacing || defaultSetSpacing;

	return {
		/**
		 * Shows the header spacer and positions the header on top of the overlay
		 */
		show: () => {
			// Increment the global counter
			_headerSpacerCount++;

			// Create the spacer if this is the first overlay being shown
			if ( _headerSpacerCount === 1 && !_headerSpacer ) {
				_headerSpacer = document.createElement( 'div' );
				_headerSpacer.style.width = '100%';
				_headerSpacer.classList.add( 'js-header-spacer' );
				_headerSpacer.style.height = headerHeight;

				window.requestAnimationFrame( () => {
					element.parentNode.insertBefore( _headerSpacer, element );
					Header.the.toggleOnOverlay();
				} );
			} else if ( _headerSpacer ) {
				// Update height if spacer already exists
				_headerSpacer.style.height = headerHeight;
			}

			// Apply spacing to this specific overlay
			applySpacing( overlay, headerHeight );
		},

		/**
		 * Hides the header spacer and restores the original header position
		 */
		hide: () => {
			// Decrement the counter (never below 0)
			_headerSpacerCount = Math.max( 0, _headerSpacerCount - 1 );

			// If counter reaches 0, remove the spacer
			if ( _headerSpacerCount === 0 && _headerSpacer ) {
				window.requestAnimationFrame( () => {
					_headerSpacer.remove();
					_headerSpacer = null;
					Header.the.toggleOnOverlay();
				} );
			}

			// Always remove spacing from this specific overlay
			if ( !setSpacing ) {
				overlay.style.paddingTop = '';
			} else {
				applySpacing( overlay, '' );
			}
		}
	};
}

/**
 * Attaches a listener to the header resize event.
 *
 * @param callback - Function to call when header size changes
 * @returns Function to remove the event listener
 */
export function onHeaderResize( callback ) {
	const handler = event => {
		if ( event.detail.text() === 'Header size was changed.' ) {
			callback( event.detail.height );
		}
	};

	document.addEventListener( 'header:resize', handler );

	return () => {
		document.removeEventListener( 'header:resize', handler );
	};
}
