const regex_return_characters = /\r/g;

/**
 * @param {string} str
 * @returns {string}
 */
export function hash(str) {
    str = str.replace(regex_return_characters, '');
    let hash = 5381;
    let i = str.length;

    while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
    return (hash >>> 0).toString(36);
}

const VOID_ELEMENT_NAMES = [
    'area',
    'base',
    'br',
    'col',
    'command',
    'embed',
    'hr',
    'img',
    'input',
    'keygen',
    'link',
    'meta',
    'param',
    'source',
    'track',
    'wbr'
];

/**
 * Returns `true` if `name` is of a void element
 * @param {string} name
 */
export function is_void(name) {
    return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
}

const RESERVED_WORDS = [
    'arguments',
    'await',
    'break',
    'case',
    'catch',
    'class',
    'const',
    'continue',
    'debugger',
    'default',
    'delete',
    'do',
    'else',
    'enum',
    'eval',
    'export',
    'extends',
    'false',
    'finally',
    'for',
    'function',
    'if',
    'implements',
    'import',
    'in',
    'instanceof',
    'interface',
    'let',
    'new',
    'null',
    'package',
    'private',
    'protected',
    'public',
    'return',
    'static',
    'super',
    'switch',
    'this',
    'throw',
    'true',
    'try',
    'typeof',
    'var',
    'void',
    'while',
    'with',
    'yield'
];

/**
 * Returns `true` if `word` is a reserved JavaScript keyword
 * @param {string} word
 */
export function is_reserved(word) {
    return RESERVED_WORDS.includes(word);
}

/**
 * @param {string} name
 */
export function is_capture_event(name) {
    return name.endsWith('capture') && name !== 'gotpointercapture' && name !== 'lostpointercapture';
}

/** List of Element events that will be delegated */
const DELEGATED_EVENTS = [
    'beforeinput',
    'click',
    'change',
    'dblclick',
    'contextmenu',
    'focusin',
    'focusout',
    'input',
    'keydown',
    'keyup',
    'mousedown',
    'mousemove',
    'mouseout',
    'mouseover',
    'mouseup',
    'pointerdown',
    'pointermove',
    'pointerout',
    'pointerover',
    'pointerup',
    'touchend',
    'touchmove',
    'touchstart'
];

/**
 * Returns `true` if `event_name` is a delegated event
 * @param {string} event_name
 */
export function is_delegated(event_name) {
    return DELEGATED_EVENTS.includes(event_name);
}

/**
 * Attributes that are boolean, i.e. they are present or not present.
 */
const DOM_BOOLEAN_ATTRIBUTES = [
    'allowfullscreen',
    'async',
    'autofocus',
    'autoplay',
    'checked',
    'controls',
    'default',
    'disabled',
    'formnovalidate',
    'hidden',
    'indeterminate',
    'ismap',
    'loop',
    'multiple',
    'muted',
    'nomodule',
    'novalidate',
    'open',
    'playsinline',
    'readonly',
    'required',
    'reversed',
    'seamless',
    'selected',
    'webkitdirectory'
];

/**
 * Returns `true` if `name` is a boolean attribute
 * @param {string} name
 */
export function is_boolean_attribute(name) {
    return DOM_BOOLEAN_ATTRIBUTES.includes(name);
}

/**
 * @type {Record<string, string>}
 * List of attribute names that should be aliased to their property names
 * because they behave differently between setting them as an attribute and
 * setting them as a property.
 */
const ATTRIBUTE_ALIASES = {
    // no `class: 'className'` because we handle that separately
    formnovalidate: 'formNoValidate',
    ismap: 'isMap',
    nomodule: 'noModule',
    playsinline: 'playsInline',
    readonly: 'readOnly',
    defaultvalue: 'defaultValue',
    defaultchecked: 'defaultChecked',
    srcobject: 'srcObject'
};

/**
 * @param {string} name
 */
export function normalize_attribute(name) {
    name = name.toLowerCase();
    return ATTRIBUTE_ALIASES[name] ? ? name;
}

const DOM_PROPERTIES = [
    ...DOM_BOOLEAN_ATTRIBUTES,
    'formNoValidate',
    'isMap',
    'noModule',
    'playsInline',
    'readOnly',
    'value',
    'inert',
    'volume',
    'defaultValue',
    'defaultChecked',
    'srcObject'
];

/**
 * @param {string} name
 */
export function is_dom_property(name) {
    return DOM_PROPERTIES.includes(name);
}

const NON_STATIC_PROPERTIES = ['autofocus', 'muted', 'defaultValue', 'defaultChecked'];

/**
 * Returns `true` if the given attribute cannot be set through the template
 * string, i.e. needs some kind of JavaScript handling to work.
 * @param {string} name
 */
export function cannot_be_set_statically(name) {
    return NON_STATIC_PROPERTIES.includes(name);
}

/**
 * Subset of delegated events which should be passive by default.
 * These two are already passive via browser defaults on window, document and body.
 * But since
 * - we're delegating them
 * - they happen often
 * - they apply to mobile which is generally less performant
 * we're marking them as passive by default for other elements, too.
 */
const PASSIVE_EVENTS = ['touchstart', 'touchmove'];

/**
 * Returns `true` if `name` is a passive event
 * @param {string} name
 */
export function is_passive_event(name) {
    return PASSIVE_EVENTS.includes(name);
}

const CONTENT_EDITABLE_BINDINGS = ['textContent', 'innerHTML', 'innerText'];

/** @param {string} name */
export function is_content_editable_binding(name) {
    return CONTENT_EDITABLE_BINDINGS.includes(name);
}

const LOAD_ERROR_ELEMENTS = [
    'body',
    'embed',
    'iframe',
    'img',
    'link',
    'object',
    'script',
    'style',
    'track'
];

/**
 * Returns `true` if the element emits `load` and `error` events
 * @param {string} name
 */
export function is_load_error_element(name) {
    return LOAD_ERROR_ELEMENTS.includes(name);
}

const SVG_ELEMENTS = [
    'altGlyph',
    'altGlyphDef',
    'altGlyphItem',
    'animate',
    'animateColor',
    'animateMotion',
    'animateTransform',
    'circle',
    'clipPath',
    'color-profile',
    'cursor',
    'defs',
    'desc',
    'discard',
    'ellipse',
    'feBlend',
    'feColorMatrix',
    'feComponentTransfer',
    'feComposite',
    'feConvolveMatrix',
    'feDiffuseLighting',
    'feDisplacementMap',
    'feDistantLight',
    'feDropShadow',
    'feFlood',
    'feFuncA',
    'feFuncB',
    'feFuncG',
    'feFuncR',
    'feGaussianBlur',
    'feImage',
    'feMerge',
    'feMergeNode',
    'feMorphology',
    'feOffset',
    'fePointLight',
    'feSpecularLighting',
    'feSpotLight',
    'feTile',
    'feTurbulence',
    'filter',
    'font',
    'font-face',
    'font-face-format',
    'font-face-name',
    'font-face-src',
    'font-face-uri',
    'foreignObject',
    'g',
    'glyph',
    'glyphRef',
    'hatch',
    'hatchpath',
    'hkern',
    'image',
    'line',
    'linearGradient',
    'marker',
    'mask',
    'mesh',
    'meshgradient',
    'meshpatch',
    'meshrow',
    'metadata',
    'missing-glyph',
    'mpath',
    'path',
    'pattern',
    'polygon',
    'polyline',
    'radialGradient',
    'rect',
    'set',
    'solidcolor',
    'stop',
    'svg',
    'switch',
    'symbol',
    'text',
    'textPath',
    'tref',
    'tspan',
    'unknown',
    'use',
    'view',
    'vkern'
];

/** @param {string} name */
export function is_svg(name) {
    return SVG_ELEMENTS.includes(name);
}

const MATHML_ELEMENTS = [
    'annotation',
    'annotation-xml',
    'maction',
    'math',
    'merror',
    'mfrac',
    'mi',
    'mmultiscripts',
    'mn',
    'mo',
    'mover',
    'mpadded',
    'mphantom',
    'mprescripts',
    'mroot',
    'mrow',
    'ms',
    'mspace',
    'msqrt',
    'mstyle',
    'msub',
    'msubsup',
    'msup',
    'mtable',
    'mtd',
    'mtext',
    'mtr',
    'munder',
    'munderover',
    'semantics'
];

/** @param {string} name */
export function is_mathml(name) {
    return MATHML_ELEMENTS.includes(name);
}

const RUNES = /** @type {const} */ ([
    '$state',
    '$state.raw',
    '$state.snapshot',
    '$props',
    '$bindable',
    '$derived',
    '$derived.by',
    '$effect',
    '$effect.pre',
    '$effect.tracking',
    '$effect.root',
    '$inspect',
    '$inspect().with',
    '$inspect.trace',
    '$host'
]);

/**
 * @param {string} name
 * @returns {name is RUNES[number]}
 */
export function is_rune(name) {
    return RUNES.includes( /** @type {RUNES[number]} */ (name));
}

/** List of elements that require raw contents and should not have SSR comments put in them */
const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', 'title']);

/** @param {string} name */
export function is_raw_text_element(name) {
    return RAW_TEXT_ELEMENTS.includes( /** @type {RAW_TEXT_ELEMENTS[number]} */ (name));
}

/**
 * Prevent devtools trying to make `location` a clickable link by inserting a zero-width space
 * @param {string | undefined} location
 */
export function sanitize_location(location) {
    return location ? .replace(/\//g, '/\u200b');
}