import { getRandomId } from "@spectrum-css/preview/decorators";
import { html } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { styleMap } from "lit/directives/style-map.js";
import { unsafeSVG } from "lit/directives/unsafe-svg.js";

import "../index.css";

/**
 * @typedef { keyof import("./icon.stories.js").default.args } IconArgs
 * @typedef { IconArgs & { scale: string, setName: 'workflow' | 'ui' } } IconProps
 */

const fetchIconDetails = ({
	icons,
	workflowIcons = [],
	uiIcons = [],
	uiIconSizes = {},
}) => {
	if (!icons || Object.keys(icons).length == 0) {
		// Fetch loaded data if not provided
		if (window.icons) icons = window.icons;
		else {
			return {
				workflowIcons: [],
				uiIcons: [],
				uiIconSizes: {},
				uiIconsWithDirections: [],
			};
		}
	}

	// clean up loaded icon data
	icons = Object.entries(icons).reduce((acc, [setName, data]) => {
		acc[setName] = Object.entries(data).reduce((acc, [size, data]) => {
			acc[size] = Object.entries(data).reduce((acc, [iconName, svg]) => {
				// simplify icon name
				iconName = iconName.split("/").pop().replace(/\.svg$/, "");
				acc[iconName] = svg;

				// Add the icon name to the workflowIcons list if it's from the workflow set
				if (setName === "workflow") {
					workflowIcons.push(iconName);
				}
				else {
					const iconNameRoot = iconName.replace(/\d{2,3}$/, "").replace(/(Right|Left|Down|Up)$/, "");
					const iconNameSize = iconName.match(/\d{2,3}/g)?.[0];
					uiIcons.push(iconNameRoot);
					uiIconSizes[iconNameRoot] = [
						...new Set([
							...uiIconSizes[iconNameRoot] ?? [],
							...(iconNameSize ? [iconNameSize] : []),
						])
					];
				}
				return acc;
			}, {});
			return acc;
		}, {});
		return acc;
	}, {});

	return {
		icons,
		workflowIcons: [...new Set(workflowIcons)],
		uiIcons: [...new Set(uiIcons)],
		uiIconSizes,
		uiIconsWithDirections: [
			...new Set([
				...(uiIcons.filter((c) => !["Chevron", "Arrow"].includes(c)) ?? []),
				"ArrowRight",
				"ArrowLeft",
				"ArrowUp",
				"ArrowDown",
				"ChevronRight",
				"ChevronLeft",
				"ChevronUp",
				"ChevronDown",
			])
		],
	};
};

/**
 * Template for rendering an icon
 * @description Icon template that renders an icon based on the provided icon name and set name.
 * @param {IconProps} props
 * @param {string} props.rootClass
 * @param {"xs"|"s"|"m"|"l"|"xl"|"xxl"} props.size
 * @param {"ui"|"workflow"} props.setName
 * @param {string} props.iconName Icon name; could be from either icon set.
 * @param {string} props.uiIconName Icon name selected from the UI icon set. When defined, takes precedence over iconName when setName == "ui".
 * @param {string} props.fill
 * @param {string} props.id
 * @param {string[]} props.customClasses
 * @returns {import('lit').TemplateResult<1>}
 */
export const Template = ({
	rootClass = "spectrum-Icon",
	size = "m",
	setName,
	iconName,
	uiIconName,
	fill,
	id = getRandomId("icon"),
	customClasses = [],
	icons,
	useRef = true,
	workflowIcons,
	uiIcons,
	uiIconSizes,
} = {}, context = {}) => {
	const { globals = {}, loaded = {} } = context;

	const scale = globals.scale ?? "medium";

	if (!workflowIcons || !uiIcons || !uiIconSizes) {
		const details = fetchIconDetails({
			icons: loaded.icons,
			workflowIcons,
			uiIcons,
			uiIconSizes
		});

		if(details.icons) {
			icons = details.icons;
		}

		if (!workflowIcons && details.workflowIcons) {
			workflowIcons = details.workflowIcons;
		}

		if (!uiIcons && details.uiIcons) {
			uiIcons = details.uiIcons;
		}

		if (!uiIconSizes && details.uiIconSizes) {
			uiIconSizes = details.uiIconSizes;
		}
	}

	// UI icons are selected from a different control.
	if (setName === "ui" && uiIconName) {
		iconName = uiIconName;
	}

	if (!iconName) {
		console.warn(
			"Icon: Could not render a result because no icon name was provided to the icon template."
		);
		return html``;
	}

	let idKey = iconName;

	// If icon set was not provided, try determine which icon set contains this icon.
	// Note: icon sets can contain the same icon name, with different icons.
	if (!["workflow","ui"].includes(setName)) {
		if (workflowIcons.includes(idKey)) {
			setName = "workflow";
		}
		else if (uiIcons.includes(idKey.replace(/\d{2,3}$/, "").replace(/(Right|Left|Down|Up)$/, ""))) {
			setName = "ui";
		}
	}

	// Prefer full SVG for UI icons because sizing is more consistent.
	if (setName === "ui" && uiIconName) {
		useRef = false;
	}

	if (!setName) {
		console.warn(
			`Icon: Could not determine the icon set for the provided icon name: ${idKey}.`
		);
		return html``;
	}

	// If a descriptor like Right, Left, Down, or Up is present for the UI icons Chevron or
	// Arrow, use that only for the class and not the icon fetch.
	if (
		setName == "ui" &&
		uiIcons.some((c) => idKey.startsWith(c)) &&
		["Right", "Left", "Down", "Up"].some((c) => idKey.includes(c))
	) {
		idKey = idKey.replace(/(Right|Left|Down|Up)/, "");
	}

	/**
	 * Fallback UI Icon sizing number.
	 *
	 * If the icon name includes its scale, we want to leave that scale. This is preferred,
	 * as UI icons do not use workflow icon sizing.
	 *
	 * If the UI icon name does not include scale, reformat it to match the provided sizing.
	 * E.g. with a size of "s", the icon name "ChevronRight" would become "ChevronRight75".
	 */
	if (
		setName == "ui" &&
		// Exists in the list of available UI icons.
		uiIcons.includes(idKey.replace(/\d{2,3}$/, "")) &&
		// Does not already have size number at the end.
		!idKey.match(/^(?!\d).*\d{2,3}$/) &&
		// Exclude some UI icons that do not (yet) have size numbers.
		uiIconSizes[idKey]?.length != 0
	) {
		let sizeVal;
		switch (size) {
			case "xs":
			case "s":
				sizeVal = "75";
				break;
			case "l":
				sizeVal = "200";
				break;
			case "xl":
			case "xxl":
				sizeVal = "300";
				break;
			default:
				sizeVal = "100";
				break;
		}

		idKey += sizeVal;
		iconName += sizeVal;
	}

	// Fetch SVG file markup, and set optional fill color.
	let inlineStyle;
	if (fill) inlineStyle = `color: ${fill}`;

	let svgString;
	if (!useRef && icons && icons[setName]?.[scale]?.[idKey]) {
		svgString = icons[setName][scale][idKey];
	}

	/**
	 * Classes to apply to the SVG element. Object as used by the classMap function.
	 * @type {[name: string]: string | boolean | number}
	 */
	const classList = {
		[rootClass]: true,
		[`spectrum-UIIcon-${iconName}`]: !!(setName === "ui"),
		[`${rootClass}--${scale}`]: !!(setName === "ui" && scale),
		[`${rootClass}--size${size?.toUpperCase()}`]: !!(
			(!setName || setName === "workflow") &&
			size
		),
		...customClasses.reduce((a, c) => ({ ...a, [c]: true }), {}),
	};

	if (svgString) {
		const classesAsString = Object.entries(classList).reduce((acc, [key, value]) => {
			if (value) acc += `${key} `;
			return acc;
		}, "");

		return html`${unsafeSVG(
			svgString.replace(/<svg/, `<svg class="${classesAsString}" focusable="false" aria-hidden="true" role="img"`)
		)}`;
	}

	// ui ID: #spectrum-css-icon-${idKey}
	// workflow ID: #spectrum-icon-(18|24)-${idKey}
	const iconID =
		setName !== "workflow"
			? `spectrum-css-icon-${idKey}`
			: `spectrum-icon-${scale !== "medium" ? "24" : "18"}-${idKey}`;

	return html`<svg
		class=${classMap(classList)}
		id=${ifDefined(id)}
		style=${ifDefined(inlineStyle)}
		focusable="false"
		aria-hidden="true"
		aria-labelledby=${idKey}
		role="img"
	>
		<title id=${idKey}>${idKey.replace(/([A-Z])/g, " $1").trim()}</title>
		<use xlink:href="#${iconID}" href="#${iconID}" />
	</svg>`;
};

/**
 * Helper template function to display multiple icons using an array of icon names.
 */
export const IconListTemplate = (args, iconsList = [], context) => html`
	<div
		style=${styleMap({
			"display": "flex",
			"gap": "32px",
			"flexWrap": "wrap",
		})}
	>
		${iconsList.map(
			(iconName) => Template({ ...args, iconName }, context)
		)}
	</div>
`;

/**
 * A sampling of a few UI icons.
 */
export const UIDefaultTemplate = (args, context) => html`
<div style="margin-bottom: 32px;">
	${IconListTemplate(
		{
			...args,
			setName: "ui",
		},
		[
			"Asterisk100",
			"Asterisk200",
			"Asterisk300",
		],
		context
	)}
</div>
<div>
	${IconListTemplate(
		{
			...args,
			setName: "ui",
		},
		[
			"ChevronDown50",
			"ChevronDown75",
			"ChevronDown100",
			"ChevronDown200",
			"ChevronDown300",
			"ChevronDown400",
		],
		context
	)}
</div>
`;
