/**
 * @classdesc This provides a common dictionnary for a Paged.js tools.
 * 
 * Web Component will fetch the list of features. For each one a class will be instantiated. 
 * This class groups all the key data. From this list of instances 
 * the component will refine the data, import all necessary scripts, hooks and stylesheet. 
 * 
 * This very logic can be observed at the end of the component in the setup() function.
 * 
 * @author Benjamin G. <ecrire@bnjm.eu>
 * @author Julie Blanc <contact@julie-blanc.fr>
 * @tutorial https://gitlab.com/csspageweaver/csspageweaver/-/wikis/home
 */

class CssPageWeaver_Dict {
	constructor(){
		console.log('CSS Page Weaver Dict initialized');

		// Define a Feature class to manage individual features
		this.Feature = class {
		  constructor(featureConfig, id, directory, config) {
			this.id = id
			this.directory = directory
			this.ui = featureConfig.ui
			this.parameters = featureConfig.parameters
			this.hook = featureConfig.hook
			this.script = featureConfig.script
			this.stylesheet = featureConfig.stylesheet
			this.globalParameters = config
		  }
		}

		this.setup()
	}

	/*-- Generics functions --*/
	
	/**
	 * Clear and split feature directory path to define an unique ID
	 * @param {string} path - Feature folder path.
	 * @returns {string} - Feature ID.
	 */
	getIdFromPath(path){
		return path.trim().split("/").filter(segment => segment.trim() !== '').pop()
	}

	/*-- Dict 		--*/

	setSharedDictionnary(){

		window.cssPageWeaver = {}
		// Get the document title and remove spaces for use as a variable
		cssPageWeaver.docTitle =  document.getElementsByTagName("title")[0].text.replace(/ /g, "");

		// Set path & directory
		cssPageWeaver.directory = {}
		cssPageWeaver.directory.root = `${window.location.origin}/csspageweaver`
		cssPageWeaver.directory.plugins = `${cssPageWeaver.directory.root}/plugins`

		// Initialise user custom files
		cssPageWeaver.user = {}

		// Object to hold all features
		cssPageWeaver.features = {}
	}

	/*-- Import or list files   --*/

	/**
	 * Import the list of feature names from the manifest file or directory.
	 *
	 * This method attempts to import the feature names from a manifest.json file.
	 * If the manifest is not found, it falls back to listing the feature names
	 * from the plugin directory.
	 *
	 * @returns {Promise<Array>} A promise that resolves to an array of feature names.
	 */
	async importManifest(){
		
		try{
			// Attempt to read the manifest file to get the list of features
			return await this.importJson(`${cssPageWeaver.directory.root}/`,`manifest.json`);
		} catch(error){
			console.log('Manifest not found, trying alternative method.');
			// If the manifest is not found, list feature names from the plugin directory
			let featuresNamesList = await this.listDir(cssPageWeaver.directory.plugins);
			// Return object
			return {"plugins": featuresNamesList }
		}

	}

	/**
	 * Imports an HTML template file from a given directory and file name.
	 * @param {string} dir - The directory where the template file is located.
	 * @param {string} file - The name of the template file.
	 * @returns {Promise<string>} - A promise that resolves to the contents of the template file.
	 */
	async importTemplate(dir, file) {
		const response = await fetch(`${dir}/${file}`);
		const template = await response.text();
		return template;
	}

	/**
	 * Imports a Javascript file from a specified directory.
	 * 
	 * @param {string} path - The path path where the JS file is located.
	 * @returns {Promise<Object>} A promise that resolves to the JS content of the file.
	 * @throws {Error} Throws an error if the fetch request fails or if the file is not found.
	 */
	async importJs(path) {
		try {
			return await import(path)
		} catch (error) {
			console.error(`Error loading JS for ${path}:`, error);
		}
	}

	/**
	 * Imports a JSON file from a specified directory.
	 *
	 * @param {string} dir - The directory path where the JSON file is located.
	 * @param {string} file - The name of the JSON file to import.
	 * @returns {Promise<Object>} A promise that resolves to the JSON content of the file.
	 * @throws {Error} Throws an error if the fetch request fails or if the file is not found.
	 */
	async importJson(dir, file) {

		try {
			const response = await fetch(`${dir}/${file}`);
			if (!response.ok) {
				throw new Error(`🚨 Oups. Can't find ${file} in ${this.getIdFromPath(dir)}`);
			}
			const json = await response.json();
			return json;
		} catch (error) {
			console.error(`Error fetching json file in ${this.getIdFromPath(dir)}:`, error);
			throw error; // Re-throw the error to be caught in importJson
		}
	}

	/**
	 * Lists the directories within a specified directory by fetching and parsing its HTML content.
	 *
	 * This method fetches the automatically generated HTML page listing content of a directory, 
	 * parses it to find links, and extracts directory names based on a date pattern.
	 * 
	 * Server must allow listing directory.
	 * Yes. It's dirty.
	 *
	 * @param {string} dir - The URL of the directory to list.
	 * @returns {Promise<Array<string>>} A promise that resolves to an array of directory names.
	 * @throws {Error} Throws an error if the fetch request fails or if there is an issue parsing the content.
	 */
	async listDir(dir) {
	  try {
		const response = await fetch(dir);
		if (!response.ok) {
		  throw new Error('Failed to fetch plugin directory');    }
		const text = await response.text();

		const parser = new DOMParser();
		const doc = parser.parseFromString(text, 'text/html');
		const links = doc.querySelectorAll('a');

		const directories = [];
		const datePattern = /\d+\//; // Matches the first number followed by a '/'

		links.forEach(link => {
			const name = link.textContent;
			if (name) {
				const match = name.match(datePattern);
				if (match) {
					// Remove everything from the date part onward
					const cleanedName = name.split(datePattern)[0].trim();
					directories.push(cleanedName);
				}
			}
		});

		return directories;
	  } catch (error) {
			console.error(`Error listing ${dir} items`, error);
		throw error
	  }
	}


	/*-- CSS 		--*/

	/**
	 * Compare declared and DOM loaded stylesheet. Warn user if he missed one.
	 * {array} - An array of stylesheet declared by user in manifest 
	 */
	lookForForgottenStylesheet(userStylesheets) {
		// Get all stylesheet (not for screen) already loaded
		const links = document.querySelectorAll(`link[rel="stylesheet"]:not([media="screen"]`)

		let domStylesheets = []

		// Iterate over each link element
		links.forEach(link => {
			// Exlude CSS Page Weaver stylesheet
			if(!link.href.includes('csspageweaver')){
				domStylesheets.push(link.href.split('?')[0])
			}
		});

		// We'll not retrieve already loaded CSS just warn user if he forget one stylesheet
		//return CSSArray

		let missedStylesheets = domStylesheets.filter(sheet => !userStylesheets.includes(sheet));

		if(missedStylesheets.length > 0){
			console.warn(`😶‍🌫️ Did you missed to include ${missedStylesheets.join(', ') } into csspageweaver/manifest.json? `)
		}
	}

	
	/*-- Features 	--*/
	
	/**
	 * Initializes features by loading their configurations and creating instances.
	 * 
	 * This method reads feature names from a manifest, imports their configurations,
	 * and sets up their UI, hooks and scripts if available.
	 */
	async initializeFeatures(featuresNamesList, featuresParameters) {

		// Loop all features
		for (const featureName of featuresNamesList) {
			const featureDir = `${cssPageWeaver.directory.plugins}/${featureName}/`

			const featureEmbedConfig =  await this.importJson(featureDir, `config.json`)
			const featureManifestConfig = {"parameters" : featuresParameters[featureName]} || {}
			const featureConfig = {...featureEmbedConfig, ...featureManifestConfig}

			// Create a new instance of the feature
			let feature = new this.Feature(featureConfig, featureName, featureDir, cssPageWeaver.parameters);

			// Import feature's HTML template if specified
			if(feature.ui){
				if(feature.ui.template){
					feature.ui.html = await this.importTemplate(featureDir, feature.ui.template);
				}
			}

			// Import the feature's script and hook if specified
			['script', 'hook'].forEach(property => {
				if (feature[property]) {
					let fileName = feature[property];
					feature[property] = this.importJs(`${featureDir}/${fileName}`);
				}
			});
			
			// Store the initialized feature			
			cssPageWeaver.features[featureName] = feature

		}
	}

	async initializeUserHook(handlerList = []) {
		cssPageWeaver.user.hook = await Promise.all(
			handlerList.map(async path => {
			    return await this.importJs(path) // Return the promise from importJs
			})
		)
	}

	/**
	 * Retrieves features that have a hook from the `cssPageWeaver` object.
	 * @returns {Array} An array of features that have a hook. Returns an empty array if `cssPageWeaver` is undefined or does not have features.
	 */
	getFeaturesHookAsArray(){
	    // Convert the features object to an array
	    const featuresArray = Object.values(cssPageWeaver.features);
	    // Filter to only features with a hook
	    return featuresArray
	        .filter(feature => feature.hook)
	        .map(feature => feature.hook);
	}

	/**
	 * Aggregates the CSS paths from all features that have a stylesheet.
	 * @returns {Array} An array of CSS file paths.
	 */
	getFeaturesStyleAsArray(){
		// Convert the features object to an array
		const featuresArray = Object.values(cssPageWeaver.features);
		// Filter to only features with a hook
		return featuresArray
			.filter(feature => feature.stylesheet)
			.map(feature => `${feature.directory}${feature.stylesheet}`);
	}

	/*-- Setup 		--*/

	async setup(){

		// Set Shared dict		
		this.setSharedDictionnary()

		// Import Manifest
		let manifest = await this.importManifest()

		// Get features list as an array of class for each
		await this.initializeFeatures(manifest.plugins, manifest.pluginsParameters)
		
		// list user stylesheets
		cssPageWeaver.user.css = manifest.css || []

		// list stylesheets as convenient object
		cssPageWeaver.stylesheet = {
			features: this.getFeaturesStyleAsArray(),
			user: cssPageWeaver.user.css
		}

		// Warn user if he missed a stylesheet
		this.lookForForgottenStylesheet(cssPageWeaver.user.css)

		// Import User handlers
		await this.initializeUserHook(manifest.hook)

        // Dispatch an event to signal that features are loaded
        const event = new Event('cssPageWeaver-dictInit');
        document.dispatchEvent(event)

	}

}

export default CssPageWeaver_Dict