/**
 * @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' );
const fs = require( 'fs' );
const path = require( 'path' );

const HexoManager = require( '../../src/hexo-manager' );

/**
 * Cache for parsed SVG elements.
 */
const SVG_CACHE = new Map();

module.exports = function spritesheetSvg( str, isDocument = true ) {
	const $ = cheerio.load( str, null, isDocument );
	const elements = $( '[data-spritesheet-svg]' );

	// Skip if there are no elements with data-spritesheet-svg
	if ( elements.length === 0 ) {
		return str;
	}

	// Collect all unique SVG files to include in the spritesheet
	const svgFiles = new Map();

	// Helper function to extract SVG file information
	function collectSvgFileInfo( element ) {
		const svgPath = element.attr( 'data-spritesheet-svg' );
		const iconId = element.attr( 'data-spritesheet-id' );

		if ( !svgFiles.has( iconId ) ) {
			svgFiles.set( iconId, svgPath );
		}
	}

	// Process normal SVG elements
	elements.each( function() {
		const $element = $( this );

		collectSvgFileInfo( $element );
	} );

	// Process and remove preload elements
	const preloadElements = $( '[data-spritesheet-preload="true"]' );
	preloadElements.each( function() {
		const $element = $( this );

		collectSvgFileInfo( $element );
		$element.remove();
	} );

	if ( svgFiles.size === 0 ) {
		return str;
	}

	// Create spritesheet and get viewBox data
	const { spritesheetElement, iconViewBoxes } = createSpritesheet( $, svgFiles );

	// Add spritesheet to the beginning of the body
	if ( $( 'body' ).length ) {
		$( 'body' ).prepend( spritesheetElement );
	}

	// Set viewBox for original SVG elements
	elements.each( function() {
		const element = $( this );
		const iconId = element.attr( 'data-spritesheet-id' );
		const viewBox = iconViewBoxes.get( iconId );

		if ( viewBox && this.tagName.toLowerCase() === 'svg' ) {
			element.attr( 'viewBox', viewBox );
		}
	} );

	$( '[data-spritesheet-svg]' ).removeAttr( 'data-spritesheet-svg' );
	$( '[data-spritesheet-id]' ).removeAttr( 'data-spritesheet-id' );

	return $.html();
};

/**
 * Creates an SVG spritesheet from the provided SVG files.
 * Returns an object containing the spritesheet element and a map of icon IDs to their viewBox strings.
 */
function createSpritesheet( $, svgFiles ) {
	const symbols = [];
	const iconViewBoxes = new Map();

	for ( const [ iconId, svgPath ] of svgFiles.entries() ) {
		const absolutePath = path.join( HexoManager.hexo.theme_dir, svgPath );

		if ( !fs.existsSync( absolutePath ) ) {
			console.warn( `SVG file not found: ${ absolutePath }` );
			continue;
		}

		// Check if we have the SVG in cache
		if ( !SVG_CACHE.has( absolutePath ) ) {
			try {
				const svgContent = fs.readFileSync( absolutePath, 'utf8' );
				const $svg = cheerio.load( svgContent, { xmlMode: true } )( 'svg' );
				SVG_CACHE.set( absolutePath, $svg );
			} catch ( error ) {
				console.error( `Error reading SVG file: ${ absolutePath }`, error );
				continue;
			}
		}

		const $svg = SVG_CACHE.get( absolutePath ).clone();

		// Create symbol element from SVG
		const $symbol = $( '<symbol></symbol>' );
		$symbol.attr( 'id', iconId );

		// Copy viewBox and other relevant attributes
		let viewBoxValue;
		if ( $svg.attr( 'viewBox' ) ) {
			viewBoxValue = $svg.attr( 'viewBox' );
		} else if ( $svg.attr( 'width' ) && $svg.attr( 'height' ) ) {
			viewBoxValue = `0 0 ${ $svg.attr( 'width' ) } ${ $svg.attr( 'height' ) }`;
		}

		if ( viewBoxValue ) {
			$symbol.attr( 'viewBox', viewBoxValue );
			iconViewBoxes.set( iconId, viewBoxValue );
		}

		// Copy all child elements from SVG to symbol
		$svg.children().each( function() {
			const $child = $( this );
			// Skip title elements as they'll be provided by the consuming SVG
			if ( $child[ 0 ].tagName.toLowerCase() !== 'title' ) {
				$symbol.append( $child.clone() );
			}
		} );

		symbols.push(
			resetFillColor( $, $symbol )
		);
	}

	// Create the spritesheet container
	const $spritesheet = $( '<svg></svg>' );
	$spritesheet.attr( {
		'class': 'svg-spritesheet',
		'xmlns': 'http://www.w3.org/2000/svg',
		'style': 'display: none;',
		'aria-hidden': 'true'
	} );

	// Add all symbols to the spritesheet
	symbols.forEach( $symbol => {
		$spritesheet.append( $symbol );
	} );

	return {
		spritesheetElement: $spritesheet,
		iconViewBoxes
	};
}

/**
 * Resets the fill color of an SVG element to 'currentColor'.
 * This is useful for ensuring that the SVG inherits the text color from its parent.
 */
function resetFillColor( $, $svg ) {
	// Replace all fill attributes with the specified color.
	$svg.find( '[fill]' ).each( function() {
		$( this ).attr( 'fill', 'currentColor' );
	} );

	// Also check the root SVG for fill attribute.
	if ( $svg.attr( 'fill' ) ) {
		$svg.attr( 'fill', 'currentColor' );
	}

	return $svg;
}
