/** @import { ObjectExpression } from 'estree' */
/** @import { AST } from '#compiler' */
import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js';
import * as e from '../../../errors.js';

/**
 * @param {AST.SvelteOptionsRaw} node
 * @returns {AST.Root['options']}
 */
export default function read_options(node) {
	/** @type {AST.SvelteOptions} */
	const component_options = {
		start: node.start,
		end: node.end,
		// @ts-ignore
		attributes: node.attributes
	};

	if (!node) {
		return component_options;
	}

	for (const attribute of node.attributes) {
		if (attribute.type !== 'Attribute') {
			e.svelte_options_invalid_attribute(attribute);
		}

		const { name } = attribute;

		switch (name) {
			case 'runes': {
				component_options.runes = get_boolean_value(attribute);
				break;
			}
			case 'tag': {
				e.svelte_options_deprecated_tag(attribute);
				break; // eslint doesn't know this is unnecessary
			}
			case 'customElement': {
				/** @type {AST.SvelteOptions['customElement']} */
				const ce = {};
				const { value: v } = attribute;
				const value = v === true || Array.isArray(v) ? v : [v];

				if (value === true) {
					e.svelte_options_invalid_customelement(attribute);
				} else if (value[0].type === 'Text') {
					const tag = get_static_value(attribute);
					validate_tag(attribute, tag);
					ce.tag = tag;
					component_options.customElement = ce;
					break;
				} else if (value[0].expression.type !== 'ObjectExpression') {
					// Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning.
					// This is no longer necessary, but for backwards compat just skip in this case now.
					if (value[0].expression.type === 'Literal' && value[0].expression.value === null) {
						break;
					}
					e.svelte_options_invalid_customelement(attribute);
				}

				/** @type {Array<[string, any]>} */
				const properties = [];
				for (const property of value[0].expression.properties) {
					if (
						property.type !== 'Property' ||
						property.computed ||
						property.key.type !== 'Identifier'
					) {
						e.svelte_options_invalid_customelement(attribute);
					}
					properties.push([property.key.name, property.value]);
				}

				const tag = properties.find(([name]) => name === 'tag');
				if (tag) {
					const tag_value = tag[1]?.value;
					validate_tag(tag, tag_value);
					ce.tag = tag_value;
				}

				const props = properties.find(([name]) => name === 'props')?.[1];
				if (props) {
					if (props.type !== 'ObjectExpression') {
						e.svelte_options_invalid_customelement_props(attribute);
					}
					ce.props = {};
					for (const property of /** @type {ObjectExpression} */ (props).properties) {
						if (
							property.type !== 'Property' ||
							property.computed ||
							property.key.type !== 'Identifier' ||
							property.value.type !== 'ObjectExpression'
						) {
							e.svelte_options_invalid_customelement_props(attribute);
						}
						ce.props[property.key.name] = {};
						for (const prop of property.value.properties) {
							if (
								prop.type !== 'Property' ||
								prop.computed ||
								prop.key.type !== 'Identifier' ||
								prop.value.type !== 'Literal'
							) {
								e.svelte_options_invalid_customelement_props(attribute);
							}

							if (prop.key.name === 'type') {
								if (
									['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(
										/** @type {string} */ (prop.value.value)
									) === -1
								) {
									e.svelte_options_invalid_customelement_props(attribute);
								}
								ce.props[property.key.name].type = /** @type {any} */ (prop.value.value);
							} else if (prop.key.name === 'reflect') {
								if (typeof prop.value.value !== 'boolean') {
									e.svelte_options_invalid_customelement_props(attribute);
								}
								ce.props[property.key.name].reflect = prop.value.value;
							} else if (prop.key.name === 'attribute') {
								if (typeof prop.value.value !== 'string') {
									e.svelte_options_invalid_customelement_props(attribute);
								}
								ce.props[property.key.name].attribute = prop.value.value;
							} else {
								e.svelte_options_invalid_customelement_props(attribute);
							}
						}
					}
				}

				const shadow = properties.find(([name]) => name === 'shadow')?.[1];
				if (shadow) {
					const shadowdom = shadow?.value;
					if (shadowdom !== 'open' && shadowdom !== 'none') {
						e.svelte_options_invalid_customelement_shadow(shadow);
					}
					ce.shadow = shadowdom;
				}

				const extend = properties.find(([name]) => name === 'extend')?.[1];
				if (extend) {
					ce.extend = extend;
				}

				component_options.customElement = ce;
				break;
			}
			case 'namespace': {
				const value = get_static_value(attribute);

				if (value === NAMESPACE_SVG) {
					component_options.namespace = 'svg';
				} else if (value === NAMESPACE_MATHML) {
					component_options.namespace = 'mathml';
				} else if (value === 'html' || value === 'mathml' || value === 'svg') {
					component_options.namespace = value;
				} else {
					e.svelte_options_invalid_attribute_value(attribute, `"html", "mathml" or "svg"`);
				}

				break;
			}
			case 'css': {
				const value = get_static_value(attribute);

				if (value === 'injected') {
					component_options.css = value;
				} else {
					e.svelte_options_invalid_attribute_value(attribute, `"injected"`);
				}

				break;
			}
			case 'immutable': {
				component_options.immutable = get_boolean_value(attribute);
				break;
			}
			case 'preserveWhitespace': {
				component_options.preserveWhitespace = get_boolean_value(attribute);
				break;
			}
			case 'accessors': {
				component_options.accessors = get_boolean_value(attribute);
				break;
			}
			default:
				e.svelte_options_unknown_attribute(attribute, name);
		}
	}

	return component_options;
}

/**
 * @param {any} attribute
 */
function get_static_value(attribute) {
	const { value } = attribute;

	if (value === true) return true;

	const chunk = Array.isArray(value) ? value[0] : value;

	if (!chunk) return true;
	if (value.length > 1) {
		return null;
	}
	if (chunk.type === 'Text') return chunk.data;
	if (chunk.expression.type !== 'Literal') {
		return null;
	}

	return chunk.expression.value;
}

/**
 * @param {any} attribute
 */
function get_boolean_value(attribute) {
	const value = get_static_value(attribute);
	if (typeof value !== 'boolean') {
		e.svelte_options_invalid_attribute_value(attribute, 'true or false');
	}
	return value;
}

// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
const tag_name_char =
	'[a-z0-9_.\xB7\xC0-\xD6\xD8-\xF6\xF8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}-]';
const regex_valid_tag_name = new RegExp(`^[a-z]${tag_name_char}*-${tag_name_char}*$`, 'u');
const reserved_tag_names = [
	'annotation-xml',
	'color-profile',
	'font-face',
	'font-face-src',
	'font-face-uri',
	'font-face-format',
	'font-face-name',
	'missing-glyph'
];

/**
 * @param {any} attribute
 * @param {string | null} tag
 * @returns {asserts tag is string}
 */
function validate_tag(attribute, tag) {
	if (typeof tag !== 'string') {
		e.svelte_options_invalid_tagname(attribute);
	}
	if (tag) {
		if (!regex_valid_tag_name.test(tag)) {
			e.svelte_options_invalid_tagname(attribute);
		} else if (reserved_tag_names.includes(tag)) {
			e.svelte_options_reserved_tagname(attribute);
		}
	}
}
