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

'use strict';

const cheerio = require( 'cheerio' );

/**
 * Global mapping of HTML elements to their corresponding design system classes.
 */
const ELEMENTS_CLASSES_MAPPINGS = {
	'h1': 'b-h1',
	'h2': 'b-h2',
	'h3': 'b-h3',
	'h4': 'b-h4',
	'h5': 'b-h5',
	'h6': 'b-h6',
	'a': 'b-link',

	// Text formatting elements
	'p': 'b-paragraph',
	'strong': 'b-bold',
	'b': 'b-bold',
	'em': 'b-italic',
	'i': 'b-italic',
	'code': 'b-inline-code',
	'pre': 'b-code-block',
	'blockquote': 'b-quote',
	'hr': 'b-separator',
	'span': 'b-text',
	'small': 'b-small-text',
	'sub': 'b-subscript',
	'sup': 'b-superscript',

	// List elements
	'ul': [ 'b-list', 'b-list--unordered' ],
	'ol': [ 'b-list', 'b-list--ordered' ],
	'li': 'b-list__item',
	'dl': 'b-description',
	'dt': 'b-description__term',
	'dd': 'b-description__details',

	// Details
	'details': 'b-details',
	'summary': [ 'b-reset-button', 'b-details__summary' ],

	// Table elements
	'table': $element => {
		const variant = $element.data( 'variant' ) || 'striped';

		return [ 'b-table', `b-table--${ variant }` ];
	},
	'thead': 'b-table__header',
	'tbody': 'b-table__body',
	'tr': 'b-table__row',
	'td': 'b-table__cell',
	'th': [ 'b-table__cell', 'b-table__cell-header' ],

	// Image and media elements
	'img': 'b-image',
	'figure': 'b-figure',
	'figcaption': 'b-figure-caption',
	'iframe': 'b-iframe',

	// Accessibility elements
	'kbd': 'c-keyboard-shortcut c-keyboard-shortcut--raised',
	'mark': 'b-highlight'
};

/**
 * Classes that are allowed to be added to elements with existing classes.
 */
const ELEMENTS_WITH_WHITELIST_CLASSES = [
	'headerlink'
];

/**
 * Add design system classes to the parsed document elements.
 * It prevents leaking of styling to the embedded third party components such
 * as CKEditor 5, or any other components that are not part of the design system.
 */
hexo.extend.filter.register( 'after_post_render', page => {
	if ( page.projectTheme !== 'gloria' ) {
		return page;
	}

	const $ = cheerio.load( page.content, null, false );

	// Apply classes based on the global mapping
	for ( const [ element, classesOrCallback ] of Object.entries( ELEMENTS_CLASSES_MAPPINGS ) ) {
		const $elements = $( element );

		$elements.each( ( _, element ) => {
			const $element = $( element );

			if ( hasClassOrParentWithClass( $element, 'no-transform' ) ) {
				return;
			}

			// Add the 'doc' class to each element that gets styled
			$element.addClass( 'doc' );

			const classList = ( $element.attr( 'class' )?.split( /\s+/ ) || [] ).filter(
				className => !ELEMENTS_WITH_WHITELIST_CLASSES.includes( className )
			);

			// Avoid applying classes to elements that already have one.
			if ( classList.length > 1 ) {
				return;
			}

			// Add the specified classes
			if ( typeof classesOrCallback === 'function' ) {
				const elementClasses = classesOrCallback( $element );

				if ( Array.isArray( elementClasses ) ) {
					$element.addClass( elementClasses.join( ' ' ) );
				} else if ( typeof elementClasses === 'string' ) {
					$element.addClass( elementClasses );
				}
			} else if ( Array.isArray( classesOrCallback ) ) {
				$element.addClass( classesOrCallback.join( ' ' ) );
			} else {
				$element.addClass( classesOrCallback );
			}
		} );
	}

	page.content = $.html();

	return page;
}, 40 );

/**
 * Checks if the element itself or any of its parents has the specified class.
 *
 * @param $element - The jQuery element to check.
 * @param className - The class name to look for.
 * @returns True if the element or any parent has the class, false otherwise.
 */
function hasClassOrParentWithClass( $element, className ) {
	return $element.hasClass( className ) || $element.parents( `.${ className }` ).length > 0;
}
