/**
 * DOM Utilities
 * DOM操作のユーティリティ関数
 */

/**
 * 要素を作成
 * @param {string}       tag        タグ名
 * @param {Object}       attributes 属性オブジェクト
 * @param {string|Array} children   子要素
 * @return {HTMLElement}
 */
export function createElement( tag, attributes = {}, children = [] ) {
	const element = document.createElement( tag );

	// 属性を設定
	Object.entries( attributes ).forEach( ( [ key, value ] ) => {
		if ( key === 'className' ) {
			element.className = value;
		} else if ( key === 'style' && typeof value === 'object' ) {
			Object.assign( element.style, value );
		} else {
			element.setAttribute( key, value );
		}
	} );

	// 子要素を追加
	const childArray = Array.isArray( children ) ? children : [ children ];
	childArray.forEach( ( child ) => {
		if ( typeof child === 'string' ) {
			element.appendChild( document.createTextNode( child ) );
		} else if ( child instanceof HTMLElement ) {
			element.appendChild( child );
		}
	} );

	return element;
}

/**
 * クラスをトグル
 * @param {HTMLElement} element   対象要素
 * @param {string}      className クラス名
 * @param {boolean}     force     強制的に追加/削除
 */
export function toggleClass( element, className, force ) {
	if ( force !== undefined ) {
		element.classList.toggle( className, force );
	} else {
		element.classList.toggle( className );
	}
}

/**
 * 複数のクラスを追加
 * @param {HTMLElement}   element    対象要素
 * @param {Array<string>} classNames クラス名の配列
 */
export function addClasses( element, classNames ) {
	element.classList.add( ...classNames );
}

/**
 * 複数のクラスを削除
 * @param {HTMLElement}   element    対象要素
 * @param {Array<string>} classNames クラス名の配列
 */
export function removeClasses( element, classNames ) {
	element.classList.remove( ...classNames );
}

/**
 * 要素が表示されているかチェック
 * @param {HTMLElement} element 対象要素
 * @return {boolean}
 */
export function isVisible( element ) {
	return !! (
		element.offsetWidth ||
		element.offsetHeight ||
		element.getClientRects().length
	);
}

/**
 * 要素が画面内に表示されているかチェック
 * @param {HTMLElement} element 対象要素
 * @return {boolean}
 */
export function isInViewport( element ) {
	const rect = element.getBoundingClientRect();
	return (
		rect.top >= 0 &&
		rect.left >= 0 &&
		rect.bottom <=
			( window.innerHeight || document.documentElement.clientHeight ) &&
		rect.right <=
			( window.innerWidth || document.documentElement.clientWidth )
	);
}

/**
 * 最も近い親要素を検索
 * @param {HTMLElement} element  開始要素
 * @param {string}      selector セレクター
 * @return {HTMLElement|null}
 */
export function findClosest( element, selector ) {
	return element.closest( selector );
}

/**
 * 要素を削除
 * @param {HTMLElement} element 削除する要素
 */
export function removeElement( element ) {
	if ( element && element.parentNode ) {
		element.parentNode.removeChild( element );
	}
}

/**
 * すべての子要素を削除
 * @param {HTMLElement} element 対象要素
 */
export function removeAllChildren( element ) {
	while ( element.firstChild ) {
		element.removeChild( element.firstChild );
	}
}

/**
 * 要素の後に挿入
 * @param {HTMLElement} newElement       新しい要素
 * @param {HTMLElement} referenceElement 基準要素
 */
export function insertAfter( newElement, referenceElement ) {
	referenceElement.parentNode.insertBefore(
		newElement,
		referenceElement.nextSibling
	);
}

/**
 * スクロール位置を取得
 * @return {Object} {x, y}
 */
export function getScrollPosition() {
	return {
		x: window.pageXOffset || document.documentElement.scrollLeft,
		y: window.pageYOffset || document.documentElement.scrollTop,
	};
}

/**
 * 要素の位置を取得
 * @param {HTMLElement} element 対象要素
 * @return {Object} {top, left, width, height}
 */
export function getElementPosition( element ) {
	const rect = element.getBoundingClientRect();
	const scrollPos = getScrollPosition();

	return {
		top: rect.top + scrollPos.y,
		left: rect.left + scrollPos.x,
		width: rect.width,
		height: rect.height,
	};
}

export default {
	createElement,
	toggleClass,
	addClasses,
	removeClasses,
	isVisible,
	isInViewport,
	findClosest,
	removeElement,
	removeAllChildren,
	insertAfter,
	getScrollPosition,
	getElementPosition,
};
