/** @import { TemplateNode } from '#client' */
import {
    hydrate_node,
    hydrating,
    set_hydrate_node
} from './hydration.js';
import {
    DEV
} from 'esm-env';
import {
    init_array_prototype_warnings
} from '../dev/equality.js';
import {
    get_descriptor
} from '../../shared/utils.js';

// export these for reference in the compiled code, making global name deduplication unnecessary
/** @type {Window} */
export var $window;

/** @type {Document} */
export var $document;

/** @type {() => Node | null} */
var first_child_getter;
/** @type {() => Node | null} */
var next_sibling_getter;

/**
 * Initialize these lazily to avoid issues when using the runtime in a server context
 * where these globals are not available while avoiding a separate server entry point
 */
export function init_operations() {
    if ($window !== undefined) {
        return;
    }

    $window = window;
    $document = document;

    var element_prototype = Element.prototype;
    var node_prototype = Node.prototype;

    // @ts-ignore
    first_child_getter = get_descriptor(node_prototype, 'firstChild').get;
    // @ts-ignore
    next_sibling_getter = get_descriptor(node_prototype, 'nextSibling').get;

    // the following assignments improve perf of lookups on DOM nodes
    // @ts-expect-error
    element_prototype.__click = undefined;
    // @ts-expect-error
    element_prototype.__className = '';
    // @ts-expect-error
    element_prototype.__attributes = null;
    // @ts-expect-error
    element_prototype.__styles = null;
    // @ts-expect-error
    element_prototype.__e = undefined;

    // @ts-expect-error
    Text.prototype.__t = undefined;

    if (DEV) {
        // @ts-expect-error
        element_prototype.__svelte_meta = null;

        init_array_prototype_warnings();
    }
}

/**
 * @param {string} value
 * @returns {Text}
 */
export function create_text(value = '') {
    return document.createTextNode(value);
}

/**
 * @template {Node} N
 * @param {N} node
 * @returns {Node | null}
 */
/*@__NO_SIDE_EFFECTS__*/
export function get_first_child(node) {
    return first_child_getter.call(node);
}

/**
 * @template {Node} N
 * @param {N} node
 * @returns {Node | null}
 */
/*@__NO_SIDE_EFFECTS__*/
export function get_next_sibling(node) {
    return next_sibling_getter.call(node);
}

/**
 * Don't mark this as side-effect-free, hydration needs to walk all nodes
 * @template {Node} N
 * @param {N} node
 * @param {boolean} is_text
 * @returns {Node | null}
 */
export function child(node, is_text) {
    if (!hydrating) {
        return get_first_child(node);
    }

    var child = /** @type {TemplateNode} */ (get_first_child(hydrate_node));

    // Child can be null if we have an element with a single child, like `<p>{text}</p>`, where `text` is empty
    if (child === null) {
        child = hydrate_node.appendChild(create_text());
    } else if (is_text && child.nodeType !== 3) {
        var text = create_text();
        child ? .before(text);
        set_hydrate_node(text);
        return text;
    }

    set_hydrate_node(child);
    return child;
}

/**
 * Don't mark this as side-effect-free, hydration needs to walk all nodes
 * @param {DocumentFragment | TemplateNode[]} fragment
 * @param {boolean} is_text
 * @returns {Node | null}
 */
export function first_child(fragment, is_text) {
    if (!hydrating) {
        // when not hydrating, `fragment` is a `DocumentFragment` (the result of calling `open_frag`)
        var first = /** @type {DocumentFragment} */ (get_first_child( /** @type {Node} */ (fragment)));

        // TODO prevent user comments with the empty string when preserveComments is true
        if (first instanceof Comment && first.data === '') return get_next_sibling(first);

        return first;
    }

    // if an {expression} is empty during SSR, there might be no
    // text node to hydrate — we must therefore create one
    if (is_text && hydrate_node ? .nodeType !== 3) {
        var text = create_text();

        hydrate_node ? .before(text);
        set_hydrate_node(text);
        return text;
    }

    return hydrate_node;
}

/**
 * Don't mark this as side-effect-free, hydration needs to walk all nodes
 * @param {TemplateNode} node
 * @param {number} count
 * @param {boolean} is_text
 * @returns {Node | null}
 */
export function sibling(node, count = 1, is_text = false) {
    let next_sibling = hydrating ? hydrate_node : node;
    var last_sibling;

    while (count--) {
        last_sibling = next_sibling;
        next_sibling = /** @type {TemplateNode} */ (get_next_sibling(next_sibling));
    }

    if (!hydrating) {
        return next_sibling;
    }

    var type = next_sibling ? .nodeType;

    // if a sibling {expression} is empty during SSR, there might be no
    // text node to hydrate — we must therefore create one
    if (is_text && type !== 3) {
        var text = create_text();
        // If the next sibling is `null` and we're handling text then it's because
        // the SSR content was empty for the text, so we need to generate a new text
        // node and insert it after the last sibling
        if (next_sibling === null) {
            last_sibling ? .after(text);
        } else {
            next_sibling.before(text);
        }
        set_hydrate_node(text);
        return text;
    }

    set_hydrate_node(next_sibling);
    return /** @type {TemplateNode} */ (next_sibling);
}

/**
 * @template {Node} N
 * @param {N} node
 * @returns {void}
 */
export function clear_text_content(node) {
    node.textContent = '';
}