import { isBoolean, isNumber, isString, isArray, isPlainObject } from "./util/types";
import { entries } from "./util/object";
import { BBox } from "./util/bbox";
import { compose_stylesheet, apply_styles } from "./css";
import { logger } from "./logging";
const _element = (tag) => {
    return (attrs = {}, ...children) => {
        const element = document.createElement(tag);
        if (!isPlainObject(attrs)) {
            children = [attrs, ...children];
            attrs = {};
        }
        else {
            attrs = { ...attrs };
        }
        if (attrs.class != null) {
            const classes = (() => {
                if (isString(attrs.class)) {
                    return attrs.class.split(/\s+/);
                }
                else {
                    return attrs.class;
                }
            })();
            for (const cls of classes) {
                if (cls != null) {
                    element.classList.add(cls);
                }
            }
            delete attrs.class;
        }
        if (attrs.style != null) {
            if (isString(attrs.style)) {
                element.setAttribute("style", attrs.style);
            }
            else {
                apply_styles(element.style, attrs.style);
            }
            delete attrs.style;
        }
        if (attrs.data != null) {
            for (const [key, data] of entries(attrs.data)) {
                if (data != null) {
                    element.dataset[key] = data;
                }
            }
            delete attrs.data;
        }
        for (const [attr, value] of entries(attrs)) {
            if (value == null) {
                continue;
            }
            else if (isBoolean(value)) {
                element.toggleAttribute(attr, value);
            }
            else if (isNumber(value)) {
                element.setAttribute(attr, `${value}`);
            }
            else if (isString(value)) {
                element.setAttribute(attr, value);
            }
            else {
                logger.warn(`unable to set attribute: ${attr} = ${value}`);
            }
        }
        function append(child) {
            if (isString(child)) {
                element.append(document.createTextNode(child));
            }
            else if (child instanceof Node) {
                element.append(child);
            }
            else if (child instanceof NodeList || child instanceof HTMLCollection) {
                element.append(...child);
            }
            else if (child != null && child !== false) {
                throw new Error(`expected a DOM element, string, false or null, got ${JSON.stringify(child)}`);
            }
        }
        for (const child of children) {
            if (isArray(child)) {
                for (const _child of child) {
                    append(_child);
                }
            }
            else {
                append(child);
            }
        }
        return element;
    };
};
export function create_element(tag, attrs, ...children) {
    return _element(tag)(attrs, ...children);
}
export const a = _element("a");
export const abbr = _element("abbr");
export const address = _element("address");
export const area = _element("area");
export const article = _element("article");
export const aside = _element("aside");
export const audio = _element("audio");
export const b = _element("b");
export const base = _element("base");
export const bdi = _element("bdi");
export const bdo = _element("bdo");
export const blockquote = _element("blockquote");
export const body = _element("body");
export const br = _element("br");
export const button = _element("button");
export const canvas = _element("canvas");
export const caption = _element("caption");
export const cite = _element("cite");
export const code = _element("code");
export const col = _element("col");
export const colgroup = _element("colgroup");
export const data = _element("data");
export const datalist = _element("datalist");
export const dd = _element("dd");
export const del = _element("del");
export const details = _element("details");
export const dfn = _element("dfn");
export const dialog = _element("dialog");
export const div = _element("div");
export const dl = _element("dl");
export const dt = _element("dt");
export const em = _element("em");
export const embed = _element("embed");
export const fieldset = _element("fieldset");
export const figcaption = _element("figcaption");
export const figure = _element("figure");
export const footer = _element("footer");
export const form = _element("form");
export const h1 = _element("h1");
export const h2 = _element("h2");
export const h3 = _element("h3");
export const h4 = _element("h4");
export const h5 = _element("h5");
export const h6 = _element("h6");
export const head = _element("head");
export const header = _element("header");
export const hgroup = _element("hgroup");
export const hr = _element("hr");
export const html = _element("html");
export const i = _element("i");
export const iframe = _element("iframe");
export const img = _element("img");
export const input = _element("input");
export const ins = _element("ins");
export const kbd = _element("kbd");
export const label = _element("label");
export const legend = _element("legend");
export const li = _element("li");
export const link = _element("link");
export const main = _element("main");
export const map = _element("map");
export const mark = _element("mark");
export const menu = _element("menu");
export const meta = _element("meta");
export const meter = _element("meter");
export const nav = _element("nav");
export const noscript = _element("noscript");
export const object = _element("object");
export const ol = _element("ol");
export const optgroup = _element("optgroup");
export const option = _element("option");
export const output = _element("output");
export const p = _element("p");
export const picture = _element("picture");
export const pre = _element("pre");
export const progress = _element("progress");
export const q = _element("q");
export const rp = _element("rp");
export const rt = _element("rt");
export const ruby = _element("ruby");
export const s = _element("s");
export const samp = _element("samp");
export const script = _element("script");
export const search = _element("search");
export const section = _element("section");
export const select = _element("select");
export const slot = _element("slot");
export const small = _element("small");
export const source = _element("source");
export const span = _element("span");
export const strong = _element("strong");
export const style = _element("style");
export const sub = _element("sub");
export const summary = _element("summary");
export const sup = _element("sup");
export const table = _element("table");
export const tbody = _element("tbody");
export const td = _element("td");
export const template = _element("template");
export const textarea = _element("textarea");
export const tfoot = _element("tfoot");
export const th = _element("th");
export const thead = _element("thead");
export const time = _element("time");
export const title = _element("title");
export const tr = _element("tr");
export const track = _element("track");
export const u = _element("u");
export const ul = _element("ul");
export const video = _element("video");
export const wbr = _element("wbr");
export function createSVGElement(tag, attrs = null, ...children) {
    const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
    for (const [attr, value] of entries(attrs ?? {})) {
        if (value == null || value === false) {
            continue;
        }
        element.setAttribute(attr, value);
    }
    function append(child) {
        if (isString(child)) {
            element.appendChild(document.createTextNode(child));
        }
        else if (child instanceof Node) {
            element.appendChild(child);
        }
        else if (child instanceof NodeList || child instanceof HTMLCollection) {
            for (const el of child) {
                element.appendChild(el);
            }
        }
        else if (child != null && child !== false) {
            throw new Error(`expected a DOM element, string, false or null, got ${JSON.stringify(child)}`);
        }
    }
    for (const child of children) {
        if (isArray(child)) {
            for (const _child of child) {
                append(_child);
            }
        }
        else {
            append(child);
        }
    }
    return element;
}
export function text(str) {
    return document.createTextNode(str);
}
export function nbsp() {
    return text("\u00a0");
}
export function prepend(element, ...nodes) {
    const first = element.firstChild;
    for (const node of nodes) {
        element.insertBefore(node, first);
    }
}
export function empty(node, attrs = false) {
    let child;
    while ((child = node.firstChild) != null) {
        node.removeChild(child);
    }
    if (attrs && node instanceof Element) {
        for (const attr of node.attributes) {
            node.removeAttributeNode(attr);
        }
    }
}
export function contains(element, child) {
    /**
     * Like Node.contains(), but traverses Shadow DOM boundaries.
     */
    let current = child;
    while (current.parentNode != null) {
        const parent = current.parentNode;
        if (parent == element) {
            return true;
        }
        else if (parent instanceof ShadowRoot) {
            current = parent.host;
        }
        else {
            current = parent;
        }
    }
    return false;
}
export function display(element, display = true) {
    element.style.display = display ? "" : "none";
}
export function undisplay(element) {
    element.style.display = "none";
}
export function show(element) {
    element.style.visibility = "";
}
export function hide(element) {
    element.style.visibility = "hidden";
}
export function offset_bbox(element) {
    const { top, left, width, height } = element.getBoundingClientRect();
    return new BBox({
        left: left + scrollX - document.documentElement.clientLeft,
        top: top + scrollY - document.documentElement.clientTop,
        width,
        height,
    });
}
export function parent(el, selector) {
    let node = el;
    while ((node = node.parentElement) != null) {
        if (node.matches(selector)) {
            return node;
        }
    }
    return null;
}
function num(value) {
    const num = parseFloat(value);
    return isFinite(num) ? num : 0;
}
export function extents(el) {
    const style = getComputedStyle(el);
    return {
        border: {
            top: num(style.borderTopWidth),
            bottom: num(style.borderBottomWidth),
            left: num(style.borderLeftWidth),
            right: num(style.borderRightWidth),
        },
        margin: {
            top: num(style.marginTop),
            bottom: num(style.marginBottom),
            left: num(style.marginLeft),
            right: num(style.marginRight),
        },
        padding: {
            top: num(style.paddingTop),
            bottom: num(style.paddingBottom),
            left: num(style.paddingLeft),
            right: num(style.paddingRight),
        },
    };
}
export function size(el) {
    const rect = el.getBoundingClientRect();
    return {
        width: Math.ceil(rect.width),
        height: Math.ceil(rect.height),
    };
}
export function scroll_size(el) {
    return {
        width: Math.ceil(el.scrollWidth),
        height: Math.ceil(el.scrollHeight),
    };
}
export function outer_size(el) {
    const { margin: { left, right, top, bottom } } = extents(el);
    const { width, height } = size(el);
    return {
        width: Math.ceil(width + left + right),
        height: Math.ceil(height + top + bottom),
    };
}
export function content_size(el) {
    const { left, top } = el.getBoundingClientRect();
    const { padding } = extents(el);
    let width = 0;
    let height = 0;
    for (const child of (el.shadowRoot ?? el).children) {
        const rect = child.getBoundingClientRect();
        width = Math.max(width, Math.ceil(rect.left - left - padding.left + rect.width));
        height = Math.max(height, Math.ceil(rect.top - top - padding.top + rect.height));
    }
    return { width, height };
}
export function bounding_box(el) {
    const { x, y, width, height } = el.getBoundingClientRect();
    return new BBox({ x, y, width, height });
}
export function box_size(el) {
    const { width, height } = el.getBoundingClientRect();
    return { width, height };
}
export function position(el, box, margin) {
    const { style } = el;
    style.left = `${box.x}px`;
    style.top = `${box.y}px`;
    style.width = `${box.width}px`;
    style.height = `${box.height}px`;
    if (margin == null) {
        style.margin = "";
    }
    else {
        const { top, right, bottom, left } = margin;
        style.margin = `${top}px ${right}px ${bottom}px ${left}px`;
    }
}
export class ClassList {
    class_list;
    static __name__ = "ClassList";
    constructor(class_list) {
        this.class_list = class_list;
    }
    get values() {
        const values = [];
        for (let i = 0; i < this.class_list.length; i++) {
            const item = this.class_list.item(i);
            if (item != null) {
                values.push(item);
            }
        }
        return values;
    }
    has(cls) {
        return this.class_list.contains(cls);
    }
    add(...classes) {
        for (const cls of classes) {
            this.class_list.add(cls);
        }
        return this;
    }
    remove(...classes) {
        for (const cls of classes) {
            if (isArray(cls)) {
                cls.forEach((cls) => this.class_list.remove(cls));
            }
            else {
                this.class_list.remove(cls);
            }
        }
        return this;
    }
    clear() {
        for (const cls of this.values) {
            this.class_list.remove(cls);
        }
        return this;
    }
    toggle(cls, activate) {
        const add = activate != null ? activate : !this.has(cls);
        if (add) {
            this.add(cls);
        }
        else {
            this.remove(cls);
        }
        return this;
    }
}
export function classes(el) {
    return new ClassList(el.classList);
}
export function toggle_attribute(el, attr, state) {
    if (state == null) {
        state = !el.hasAttribute(attr);
    }
    if (state) {
        el.setAttribute(attr, "true");
    }
    else {
        el.removeAttribute(attr);
    }
}
export var MouseButton;
(function (MouseButton) {
    MouseButton[MouseButton["None"] = 0] = "None";
    MouseButton[MouseButton["Primary"] = 1] = "Primary";
    MouseButton[MouseButton["Secondary"] = 2] = "Secondary";
    MouseButton[MouseButton["Auxiliary"] = 4] = "Auxiliary";
    MouseButton[MouseButton["Left"] = 1] = "Left";
    MouseButton[MouseButton["Right"] = 2] = "Right";
    MouseButton[MouseButton["Middle"] = 4] = "Middle";
})(MouseButton || (MouseButton = {}));
export class StyleSheet {
    static __name__ = "StyleSheet";
    el;
    install(el) {
        el.append(this.el);
    }
    uninstall() {
        this.el.remove();
    }
}
export class InlineStyleSheet extends StyleSheet {
    static __name__ = "InlineStyleSheet";
    el = style();
    constructor(css) {
        super();
        if (isString(css)) {
            this._update(css);
        }
        else if (css != null) {
            this._update(compose_stylesheet(css));
        }
    }
    get css() {
        return this.el.textContent ?? "";
    }
    _update(css) {
        this.el.textContent = css;
    }
    clear() {
        this.replace("");
    }
    _to_css(css, styles) {
        if (styles == null) {
            return css;
        }
        else {
            return compose_stylesheet({ [css]: styles });
        }
    }
    replace(css, styles) {
        this._update(this._to_css(css, styles));
    }
    prepend(css, styles) {
        this._update(`${this._to_css(css, styles)}\n${this.css}`);
    }
    append(css, styles) {
        this._update(`${this.css}\n${this._to_css(css, styles)}`);
    }
    remove() {
        this.el.remove();
    }
}
export class GlobalInlineStyleSheet extends InlineStyleSheet {
    static __name__ = "GlobalInlineStyleSheet";
    install() {
        if (!this.el.isConnected) {
            document.head.appendChild(this.el);
        }
    }
}
export class ImportedStyleSheet extends StyleSheet {
    static __name__ = "ImportedStyleSheet";
    el;
    constructor(url) {
        super();
        this.el = link({ rel: "stylesheet", href: url });
    }
    replace(url) {
        this.el.href = url;
    }
    remove() {
        this.el.remove();
    }
}
export class GlobalImportedStyleSheet extends ImportedStyleSheet {
    static __name__ = "GlobalImportedStyleSheet";
    install() {
        if (!this.el.isConnected) {
            document.head.appendChild(this.el);
        }
    }
}
export async function dom_ready() {
    if (document.readyState == "loading") {
        return new Promise((resolve, _reject) => {
            document.addEventListener("DOMContentLoaded", () => resolve(), { once: true });
        });
    }
}
export function px(value) {
    return isNumber(value) ? `${value}px` : value;
}
export const supports_adopted_stylesheets = "adoptedStyleSheets" in ShadowRoot.prototype;
//# sourceMappingURL=dom.js.map