import { catchError, buildError } from '../utils';
import { parsePropertyValue, getValue, setValue, connectedCallback, insertVdomAnnotations } from '../runtime';
export { Build, Host, connectedCallback, createEvent, getAssetPath, getConnect, getDocument, getElement, getMode, getValue, getWindow, h, insertVdomAnnotations, parsePropertyValue, setMode, setValue } from '../runtime';
import { constrainTimeouts, MockWindow, serializeNodeToHtml } from '../mock-doc';
import globalScripts from '@stencil/core/global-scripts';

function proxyHostElement(elm, cmpMeta) {
    if (typeof elm.componentOnReady !== 'function') {
        elm.componentOnReady = componentOnReady;
    }
    if (typeof elm.forceUpdate !== 'function') {
        elm.forceUpdate = forceUpdate;
    }
    if (cmpMeta.$members$ != null) {
        const hostRef = getHostRef(elm);
        const members = Object.entries(cmpMeta.$members$);
        members.forEach(([memberName, m]) => {
            const memberFlags = m[0];
            if (memberFlags & 31) {
                const attributeName = (m[1] || memberName);
                const attrValue = elm.getAttribute(attributeName);
                if (attrValue != null) {
                    const parsedAttrValue = parsePropertyValue(attrValue, memberFlags);
                    hostRef.$instanceValues$.set(memberName, parsedAttrValue);
                }
                const ownValue = elm[memberName];
                if (ownValue !== undefined) {
                    hostRef.$instanceValues$.set(memberName, ownValue);
                }
                Object.defineProperty(elm, memberName, {
                    get() {
                        return getValue(this, memberName);
                    },
                    set(newValue) {
                        setValue(this, memberName, newValue, cmpMeta);
                    },
                    configurable: true,
                    enumerable: true
                });
            }
            else if (memberFlags & 64) {
                Object.defineProperty(elm, memberName, {
                    value() {
                        const ref = getHostRef(this);
                        const args = arguments;
                        return ref.$onReadyPromise$.then(() => ref.$lazyInstance$[memberName].apply(ref.$lazyInstance$, args)).catch(consoleError);
                    }
                });
            }
        });
    }
}
function componentOnReady() {
    return getHostRef(this).$onReadyPromise$;
}
function forceUpdate() { }

function hydrateComponent(opts, results, tagName, elm, waitPromises) {
    const Cstr = getComponent(tagName);
    if (Cstr != null) {
        const cmpMeta = Cstr.cmpMeta;
        if (cmpMeta != null) {
            if (opts.collectComponents) {
                const depth = getNodeDepth(elm);
                const cmp = results.components.find(c => c.tag === tagName);
                if (cmp) {
                    cmp.count++;
                    if (depth > cmp.depth) {
                        cmp.depth = depth;
                    }
                }
                else {
                    results.components.push({
                        tag: tagName,
                        count: 1,
                        depth: depth
                    });
                }
            }
            if (results.hydratedCount >= opts.maxHydrateCount) {
                return;
            }
            results.hydratedCount++;
            const hydratePromise = new Promise(async (resolve) => {
                try {
                    registerHost(elm);
                    proxyHostElement(elm, cmpMeta);
                    connectedCallback(elm, cmpMeta);
                    await elm.componentOnReady();
                }
                catch (e) {
                    catchError(results.diagnostics, e);
                }
                resolve();
            });
            waitPromises.push(hydratePromise);
        }
    }
}
function getNodeDepth(elm) {
    let depth = 0;
    while (elm.parentNode != null) {
        depth++;
        elm = elm.parentNode;
    }
    return depth;
}

function connectElements(opts, results, elm, waitPromises, hydratedElements) {
    if (elm != null && typeof elm.nodeName === 'string') {
        const tagName = elm.nodeName.toLowerCase();
        if (!NO_HYDRATE_TAGS.has(tagName) && !elm.hasAttribute('no-prerender')) {
            if (!hydratedElements.has(elm)) {
                hydratedElements.add(elm);
                if (tagName.includes('-')) {
                    hydrateComponent(opts, results, tagName, elm, waitPromises);
                }
                else if (opts.collectAnchors && tagName === 'a') {
                    collectAnchors(results, elm);
                }
                else if (opts.collectScripts && tagName === 'script') {
                    collectScriptElement(results, elm);
                    return;
                }
                else if (opts.collectStylesheets && tagName === 'link') {
                    collectLinkElement(results, elm);
                    return;
                }
                else if (opts.collectImgs && tagName === 'img') {
                    collectImgElement(results, elm);
                    return;
                }
            }
            const children = elm.children;
            if (children != null) {
                for (let i = 0, ii = children.length; i < ii; i++) {
                    connectElements(opts, results, children[i], waitPromises, hydratedElements);
                }
            }
        }
    }
}
function collectAttributes(node, parsedElm) {
    const attrs = node.attributes;
    for (let i = 0; i < attrs.length; i++) {
        const attr = attrs.item(i);
        parsedElm[attr.nodeName.toLowerCase()] = attr.nodeValue;
    }
}
function collectAnchors(hydrateResults, elm) {
    const parsedElm = {};
    collectAttributes(elm, parsedElm);
    if (hydrateResults.anchors.some(a => a.href === parsedElm.href) === false) {
        hydrateResults.anchors.push(parsedElm);
    }
}
function collectScriptElement(hydrateResults, elm) {
    const parsedElm = {};
    const src = parsedElm.src;
    collectAttributes(elm, parsedElm);
    if (hydrateResults.scripts.some(a => a.src !== src)) {
        hydrateResults.scripts.push(parsedElm);
    }
}
function collectLinkElement(hydrateResults, elm) {
    const rel = (elm.rel || '').toLowerCase();
    if (rel === 'stylesheet') {
        const parsedElm = {};
        const href = parsedElm.href;
        collectAttributes(elm, parsedElm);
        delete parsedElm.rel;
        delete parsedElm.type;
        if (hydrateResults.styles.some(a => a.href !== href)) {
            hydrateResults.styles.push(parsedElm);
        }
    }
}
function collectImgElement(hydrateResults, elm) {
    const parsedElm = {};
    const src = parsedElm.src;
    collectAttributes(elm, parsedElm);
    if (hydrateResults.imgs.some(a => a.src !== src)) {
        hydrateResults.imgs.push(parsedElm);
    }
}
const NO_HYDRATE_TAGS = new Set([
    'code',
    'iframe',
    'input',
    'object',
    'output',
    'noscript',
    'pre',
    'select',
    'style',
    'template',
    'textarea'
]);

function collapseHtmlWhitepace(node) {
    if (node.nodeType === 3) {
        const attributes = node.attributes;
        for (let j = attributes.length - 1; j >= 0; j--) {
            const attr = attributes.item(j);
            if (!attr.value) {
                if (SAFE_TO_REMOVE_EMPTY_ATTRS.has(attr.name)) {
                    node.removeAttribute(attr.name);
                }
            }
        }
    }
    if (WHITESPACE_SENSITIVE_TAGS.has(node.nodeName)) {
        return;
    }
    let lastWhitespaceTextNode = null;
    for (let i = node.childNodes.length - 1; i >= 0; i--) {
        const childNode = node.childNodes[i];
        if (childNode.nodeType === 3 || childNode.nodeType === 8) {
            childNode.nodeValue = childNode.nodeValue.replace(REDUCE_WHITESPACE_REGEX, ' ');
            if (childNode.nodeValue === ' ') {
                if (lastWhitespaceTextNode === null) {
                    childNode.nodeValue = ' ';
                    lastWhitespaceTextNode = childNode;
                }
                else {
                    childNode.parentNode.removeChild(childNode);
                }
                continue;
            }
        }
        else if (childNode.childNodes) {
            collapseHtmlWhitepace(childNode);
        }
        lastWhitespaceTextNode = null;
    }
}
const REDUCE_WHITESPACE_REGEX = /\s\s+/g;
const WHITESPACE_SENSITIVE_TAGS = new Set(['PRE', 'SCRIPT', 'STYLE', 'TEXTAREA']);
const SAFE_TO_REMOVE_EMPTY_ATTRS = new Set([
    'class',
    'style',
]);

const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
function parseCss(css, filePath) {
    var lineno = 1;
    var column = 1;
    var srcLines;
    function updatePosition(str) {
        const lines = str.match(/\n/g);
        if (lines)
            lineno += lines.length;
        const i = str.lastIndexOf('\n');
        column = ~i ? str.length - i : column + str.length;
    }
    function position() {
        const start = { line: lineno, column: column };
        return function (node) {
            node.position = new ParsePosition(start);
            whitespace();
            return node;
        };
    }
    class ParsePosition {
        constructor(start) {
            this.start = start;
            this.end = { line: lineno, column: column };
            this.source = filePath;
        }
    }
    ParsePosition.prototype.content = css;
    const diagnostics = [];
    function error(msg) {
        if (!srcLines) {
            srcLines = css.split('\n');
        }
        const d = {
            level: 'error',
            type: 'css',
            language: 'css',
            header: 'CSS Parse',
            messageText: msg,
            absFilePath: filePath,
            lines: [{
                    lineIndex: lineno - 1,
                    lineNumber: lineno,
                    errorCharStart: column,
                    text: css[lineno - 1],
                }]
        };
        if (lineno > 1) {
            const previousLine = {
                lineIndex: lineno - 1,
                lineNumber: lineno - 1,
                text: css[lineno - 2],
                errorCharStart: -1,
                errorLength: -1
            };
            d.lines.unshift(previousLine);
        }
        if (lineno + 2 < srcLines.length) {
            const nextLine = {
                lineIndex: lineno,
                lineNumber: lineno + 1,
                text: srcLines[lineno],
                errorCharStart: -1,
                errorLength: -1
            };
            d.lines.push(nextLine);
        }
        diagnostics.push(d);
    }
    function stylesheet() {
        const rulesList = rules();
        return {
            type: 'stylesheet',
            stylesheet: {
                source: filePath,
                rules: rulesList,
                diagnostics: diagnostics
            }
        };
    }
    function open() {
        return match(/^{\s*/);
    }
    function close() {
        return match(/^}/);
    }
    function rules() {
        var node;
        const rules = [];
        whitespace();
        comments(rules);
        while (css.length && css.charAt(0) !== '}' && (node = atrule() || rule())) {
            if (node !== false) {
                rules.push(node);
                comments(rules);
            }
        }
        return rules;
    }
    function match(re) {
        const m = re.exec(css);
        if (!m)
            return;
        const str = m[0];
        updatePosition(str);
        css = css.slice(str.length);
        return m;
    }
    function whitespace() {
        match(/^\s*/);
    }
    function comments(rules) {
        var c;
        rules = rules || [];
        while (c = comment()) {
            if (c !== false) {
                rules.push(c);
            }
        }
        return rules;
    }
    function comment() {
        const pos = position();
        if ('/' !== css.charAt(0) || '*' !== css.charAt(1))
            return;
        var i = 2;
        while ('' !== css.charAt(i) && ('*' !== css.charAt(i) || '/' !== css.charAt(i + 1)))
            ++i;
        i += 2;
        if ('' === css.charAt(i - 1)) {
            return error('End of comment missing');
        }
        const str = css.slice(2, i - 2);
        column += 2;
        updatePosition(str);
        css = css.slice(i);
        column += 2;
        return pos({
            type: 'comment',
            comment: str
        });
    }
    function selector() {
        const m = match(/^([^{]+)/);
        if (!m)
            return;
        return trim(m[0])
            .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
            .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function (m) {
            return m.replace(/,/g, '\u200C');
        })
            .split(/\s*(?![^(]*\)),\s*/)
            .map(function (s) {
            return s.replace(/\u200C/g, ',');
        });
    }
    function declaration() {
        const pos = position();
        var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);
        if (!prop)
            return;
        prop = trim(prop[0]);
        if (!match(/^:\s*/))
            return error(`property missing ':'`);
        const val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/);
        const ret = pos({
            type: 'declaration',
            property: prop.replace(commentre, ''),
            value: val ? trim(val[0]).replace(commentre, '') : ''
        });
        match(/^[;\s]*/);
        return ret;
    }
    function declarations() {
        const decls = [];
        if (!open())
            return error(`missing '{'`);
        comments(decls);
        var decl;
        while (decl = declaration()) {
            if (decl !== false) {
                decls.push(decl);
                comments(decls);
            }
        }
        if (!close())
            return error(`missing '}'`);
        return decls;
    }
    function keyframe() {
        var m;
        const vals = [];
        const pos = position();
        while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
            vals.push(m[1]);
            match(/^,\s*/);
        }
        if (!vals.length)
            return;
        return pos({
            type: 'keyframe',
            values: vals,
            declarations: declarations()
        });
    }
    function atkeyframes() {
        const pos = position();
        var m = match(/^@([-\w]+)?keyframes\s*/);
        if (!m)
            return;
        const vendor = m[1];
        m = match(/^([-\w]+)\s*/);
        if (!m)
            return error(`@keyframes missing name`);
        const name = m[1];
        if (!open())
            return error(`@keyframes missing '{'`);
        var frame;
        var frames = comments();
        while (frame = keyframe()) {
            frames.push(frame);
            frames = frames.concat(comments());
        }
        if (!close())
            return error(`@keyframes missing '}'`);
        return pos({
            type: 'keyframes',
            name: name,
            vendor: vendor,
            keyframes: frames
        });
    }
    function atsupports() {
        const pos = position();
        const m = match(/^@supports *([^{]+)/);
        if (!m)
            return;
        const supports = trim(m[1]);
        if (!open())
            return error(`@supports missing '{'`);
        const style = comments().concat(rules());
        if (!close())
            return error(`@supports missing '}'`);
        return pos({
            type: 'supports',
            supports: supports,
            rules: style
        });
    }
    function athost() {
        const pos = position();
        const m = match(/^@host\s*/);
        if (!m)
            return;
        if (!open())
            return error(`@host missing '{'`);
        const style = comments().concat(rules());
        if (!close())
            return error(`@host missing '}'`);
        return pos({
            type: 'host',
            rules: style
        });
    }
    function atmedia() {
        const pos = position();
        const m = match(/^@media *([^{]+)/);
        if (!m)
            return;
        const media = trim(m[1]);
        if (!open())
            return error(`@media missing '{'`);
        const style = comments().concat(rules());
        if (!close())
            return error(`@media missing '}'`);
        return pos({
            type: 'media',
            media: media,
            rules: style
        });
    }
    function atcustommedia() {
        const pos = position();
        const m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);
        if (!m)
            return;
        return pos({
            type: 'custom-media',
            name: trim(m[1]),
            media: trim(m[2])
        });
    }
    function atpage() {
        const pos = position();
        const m = match(/^@page */);
        if (!m)
            return;
        const sel = selector() || [];
        if (!open())
            return error(`@page missing '{'`);
        var decls = comments();
        var decl;
        while (decl = declaration()) {
            decls.push(decl);
            decls = decls.concat(comments());
        }
        if (!close())
            return error(`@page missing '}'`);
        return pos({
            type: 'page',
            selectors: sel,
            declarations: decls
        });
    }
    function atdocument() {
        const pos = position();
        const m = match(/^@([-\w]+)?document *([^{]+)/);
        if (!m)
            return;
        const vendor = trim(m[1]);
        const doc = trim(m[2]);
        if (!open())
            return error(`@document missing '{'`);
        const style = comments().concat(rules());
        if (!close())
            return error(`@document missing '}'`);
        return pos({
            type: 'document',
            document: doc,
            vendor: vendor,
            rules: style
        });
    }
    function atfontface() {
        const pos = position();
        const m = match(/^@font-face\s*/);
        if (!m)
            return;
        if (!open())
            return error(`@font-face missing '{'`);
        var decls = comments();
        var decl;
        while (decl = declaration()) {
            decls.push(decl);
            decls = decls.concat(comments());
        }
        if (!close())
            return error(`@font-face missing '}'`);
        return pos({
            type: 'font-face',
            declarations: decls
        });
    }
    const atimport = _compileAtrule('import');
    const atcharset = _compileAtrule('charset');
    const atnamespace = _compileAtrule('namespace');
    function _compileAtrule(name) {
        const re = new RegExp('^@' + name + '\\s*([^;]+);');
        return function () {
            const pos = position();
            const m = match(re);
            if (!m)
                return;
            const ret = { type: name };
            ret[name] = m[1].trim();
            return pos(ret);
        };
    }
    function atrule() {
        if (css[0] !== '@')
            return;
        return atkeyframes()
            || atmedia()
            || atcustommedia()
            || atsupports()
            || atimport()
            || atcharset()
            || atnamespace()
            || atdocument()
            || atpage()
            || athost()
            || atfontface();
    }
    function rule() {
        const pos = position();
        const sel = selector();
        if (!sel)
            return error('selector missing');
        comments();
        return pos({
            type: 'rule',
            selectors: sel,
            declarations: declarations()
        });
    }
    return addParent(stylesheet());
}
function trim(str) {
    return str ? str.trim() : '';
}
function addParent(obj, parent) {
    const isNode = obj && typeof obj.type === 'string';
    const childParent = isNode ? obj : parent;
    for (const k in obj) {
        const value = obj[k];
        if (Array.isArray(value)) {
            value.forEach(function (v) { addParent(v, childParent); });
        }
        else if (value && typeof value === 'object') {
            addParent(value, childParent);
        }
    }
    if (isNode) {
        Object.defineProperty(obj, 'parent', {
            configurable: true,
            writable: true,
            enumerable: false,
            value: parent || null
        });
    }
    return obj;
}

function getSelectors(sel) {
    SELECTORS.all.length = SELECTORS.tags.length = SELECTORS.classNames.length = SELECTORS.ids.length = SELECTORS.attrs.length = 0;
    sel = sel.replace(/\./g, ' .')
        .replace(/\#/g, ' #')
        .replace(/\[/g, ' [')
        .replace(/\>/g, ' > ')
        .replace(/\+/g, ' + ')
        .replace(/\~/g, ' ~ ')
        .replace(/\*/g, ' * ')
        .replace(/\:not\((.*?)\)/g, ' ');
    const items = sel.split(' ');
    for (var i = 0; i < items.length; i++) {
        items[i] = items[i].split(':')[0];
        if (items[i].length === 0)
            continue;
        if (items[i].charAt(0) === '.') {
            SELECTORS.classNames.push(items[i].substr(1));
        }
        else if (items[i].charAt(0) === '#') {
            SELECTORS.ids.push(items[i].substr(1));
        }
        else if (items[i].charAt(0) === '[') {
            items[i] = items[i].substr(1).split('=')[0].split(']')[0].trim();
            SELECTORS.attrs.push(items[i].toLowerCase());
        }
        else if (/[a-z]/g.test(items[i].charAt(0))) {
            SELECTORS.tags.push(items[i].toLowerCase());
        }
    }
    SELECTORS.classNames = SELECTORS.classNames.sort((a, b) => {
        if (a.length < b.length)
            return -1;
        if (a.length > b.length)
            return 1;
        return 0;
    });
    return SELECTORS;
}
const SELECTORS = {
    all: [],
    tags: [],
    classNames: [],
    ids: [],
    attrs: []
};

class StringifyCss {
    constructor(opts) {
        this.usedSelectors = opts.usedSelectors;
    }
    visit(node) {
        return this[node.type](node);
    }
    mapVisit(nodes, delim) {
        var buf = '';
        delim = delim || '';
        for (var i = 0, length = nodes.length; i < length; i++) {
            buf += this.visit(nodes[i]);
            if (delim && i < length - 1)
                buf += delim;
        }
        return buf;
    }
    compile(node) {
        return node.stylesheet
            .rules.map(this.visit, this)
            .join('');
    }
    comment() {
        return '';
    }
    import(node) {
        return '@import ' + node.import + ';';
    }
    media(node) {
        const mediaCss = this.mapVisit(node.rules);
        if (mediaCss === '') {
            return '';
        }
        return '@media ' + node.media + '{' + this.mapVisit(node.rules) + '}';
    }
    document(node) {
        const documentCss = this.mapVisit(node.rules);
        if (documentCss === '') {
            return '';
        }
        const doc = '@' + (node.vendor || '') + 'document ' + node.document;
        return doc + '{' + documentCss + '}';
    }
    charset(node) {
        return '@charset ' + node.charset + ';';
    }
    namespace(node) {
        return '@namespace ' + node.namespace + ';';
    }
    supports(node) {
        const supportsCss = this.mapVisit(node.rules);
        if (supportsCss === '') {
            return '';
        }
        return '@supports ' + node.supports + '{' + supportsCss + '}';
    }
    keyframes(node) {
        const keyframesCss = this.mapVisit(node.keyframes);
        if (keyframesCss === '') {
            return '';
        }
        return '@' + (node.vendor || '') + 'keyframes ' + node.name + '{' + keyframesCss + '}';
    }
    keyframe(node) {
        const decls = node.declarations;
        return node.values.join(',') + '{' + this.mapVisit(decls) + '}';
    }
    page(node) {
        const sel = node.selectors.length
            ? node.selectors.join(', ')
            : '';
        return '@page ' + sel + '{' + this.mapVisit(node.declarations) + '}';
    }
    ['font-face'](node) {
        const fontCss = this.mapVisit(node.declarations);
        if (fontCss === '') {
            return '';
        }
        return '@font-face{' + fontCss + '}';
    }
    host(node) {
        return '@host{' + this.mapVisit(node.rules) + '}';
    }
    ['custom-media'](node) {
        return '@custom-media ' + node.name + ' ' + node.media + ';';
    }
    rule(node) {
        const decls = node.declarations;
        if (!decls.length)
            return '';
        var i, j;
        for (i = node.selectors.length - 1; i >= 0; i--) {
            const sel = getSelectors(node.selectors[i]);
            if (this.usedSelectors) {
                var include = true;
                var jlen = sel.classNames.length;
                if (jlen > 0) {
                    for (j = 0; j < jlen; j++) {
                        if (this.usedSelectors.classNames.has(sel.classNames[j])) {
                            include = false;
                            break;
                        }
                    }
                }
                if (include) {
                    jlen = sel.tags.length;
                    if (jlen > 0) {
                        for (j = 0; j < jlen; j++) {
                            if (this.usedSelectors.tags.has(sel.tags[j])) {
                                include = false;
                                break;
                            }
                        }
                    }
                }
                if (include) {
                    jlen = sel.attrs.length;
                    if (jlen > 0) {
                        for (j = 0; j < jlen; j++) {
                            if (this.usedSelectors.attrs.has(sel.attrs[j])) {
                                include = false;
                                break;
                            }
                        }
                    }
                }
                if (include) {
                    jlen = sel.ids.length;
                    if (jlen > 0) {
                        for (j = 0; j < jlen; j++) {
                            if (this.usedSelectors.ids.has(sel.ids[j])) {
                                include = false;
                                break;
                            }
                        }
                    }
                }
                if (!include) {
                    node.selectors.splice(i, 1);
                }
            }
        }
        if (node.selectors.length === 0)
            return '';
        return `${node.selectors}{${this.mapVisit(decls)}}`;
    }
    declaration(node) {
        return node.property + ':' + node.value + ';';
    }
}

class UsedSelectors {
    constructor(elm) {
        this.tags = new Set();
        this.classNames = new Set();
        this.ids = new Set();
        this.attrs = new Set();
        this.collectSelectors(elm);
    }
    collectSelectors(elm) {
        if (elm != null && elm.tagName) {
            const tagName = elm.tagName.toLowerCase();
            this.tags.add(tagName);
            const attributes = elm.attributes;
            for (let i = 0, l = attributes.length; i < l; i++) {
                const attr = attributes.item(i);
                const attrName = attr.name.toLowerCase();
                if (attrName === 'class') {
                    const classList = elm.classList;
                    for (let i = 0, l = classList.length; i < l; i++) {
                        this.classNames.add(classList.item(i));
                    }
                }
                else if (attrName === 'style') {
                    continue;
                }
                else if (attrName === 'id') {
                    this.ids.add(attr.value);
                }
                else {
                    this.attrs.add(attrName);
                }
            }
            for (let i = 0, l = elm.children.length; i < l; i++) {
                this.collectSelectors(elm.children[i]);
            }
        }
    }
}

function removeUnusedStyles(results, doc, styleElm) {
    try {
        const cssAst = parseCss(styleElm.innerHTML);
        if (cssAst.stylesheet.diagnostics.length > 0) {
            cssAst.stylesheet.diagnostics.forEach(d => {
                results.diagnostics.push(d);
            });
            return;
        }
        try {
            const usedSelectors = new UsedSelectors(doc.body);
            const stringify = new StringifyCss({ usedSelectors });
            styleElm.innerHTML = stringify.compile(cssAst);
        }
        catch (e) {
            results.diagnostics.push({
                level: 'warn',
                type: 'css',
                header: 'CSS Stringify',
                messageText: e
            });
        }
    }
    catch (e) {
        results.diagnostics.push({
            level: 'warn',
            type: 'css',
            header: 'CSS Parse',
            messageText: e
        });
    }
}

function optimizeStyles(doc, opts, results) {
    const styleElms = doc.head.querySelectorAll(`style[data-styles]`);
    if (styleElms.length === 0) {
        return;
    }
    const styleElm = mergeInlinedStyles(doc, styleElms);
    if (styleElm != null && opts.removeUnusedStyles !== false) {
        try {
            removeUnusedStyles(results, doc, styleElm);
        }
        catch (e) {
            results.diagnostics.push({
                level: 'warn',
                type: 'hydrate',
                header: 'HTML Selector Parse',
                messageText: e
            });
        }
    }
}
function mergeInlinedStyles(doc, styleElms) {
    const styleText = [];
    let styleElm;
    for (let i = styleElms.length - 1; i >= 0; i--) {
        styleElm = styleElms[i];
        styleText.push(styleElm.innerHTML);
        styleElm.remove();
        if (i === 0) {
            const mergedStyles = styleText.reverse().join('').trim();
            if (mergedStyles.length > 0) {
                doc.head.insertBefore(styleElm, doc.head.firstChild);
                return styleElm;
            }
        }
    }
    return null;
}

function relocateMetaCharset(doc) {
    const head = doc.head;
    let charsetElm = head.querySelector('meta[charset]');
    if (charsetElm == null) {
        charsetElm = doc.createElement('meta');
        charsetElm.setAttribute('charset', 'utf-8');
    }
    else {
        charsetElm.remove();
    }
    head.insertBefore(charsetElm, head.firstChild);
}

function updateCanonicalLink(doc, canonicalLinkHref) {
    let canonicalLinkElm = doc.head.querySelector('link[rel="canonical"]');
    if (canonicalLinkElm == null) {
        canonicalLinkElm = doc.createElement('link');
        canonicalLinkElm.setAttribute('rel', 'canonical');
        doc.head.appendChild(canonicalLinkElm);
    }
    canonicalLinkElm.setAttribute('href', canonicalLinkHref);
}

async function finalizeWindow(opts, results, win, doc) {
    optimizeStyles(doc, opts, results);
    if (typeof opts.title === 'string') {
        try {
            doc.title = opts.title;
        }
        catch (e) { }
    }
    if (opts.removeScripts) {
        removeScripts(doc.documentElement);
    }
    if (opts.collapseWhitespace) {
        try {
            collapseHtmlWhitepace(doc.documentElement);
        }
        catch (e) { }
    }
    if (typeof opts.canonicalLink === 'string') {
        try {
            updateCanonicalLink(doc, opts.canonicalLink);
        }
        catch (e) { }
    }
    try {
        relocateMetaCharset(doc);
    }
    catch (e) { }
    if (typeof opts.afterHydrate === 'function') {
        try {
            await opts.afterHydrate(win, opts);
        }
        catch (e) {
            catchError(results.diagnostics, e);
        }
    }
    if (opts.clientHydrateAnnotations) {
        doc.documentElement.classList.add('hydrated');
    }
    results.title = doc.title;
}
function removeScripts(elm) {
    const children = elm.children;
    for (let i = children.length - 1; i >= 0; i--) {
        const child = children[i];
        removeScripts(child);
        if (child.nodeName === 'SCRIPT') {
            child.remove();
        }
    }
}

let Url = function() { return {}; };

if (typeof URL === 'function') {
  Url = URL;

} else if (typeof require === 'function') {
  try {
    Url = require('url').URL;
  } catch (e) {}
}

function normalizeHydrateOptions(inputOpts) {
    const outputOpts = Object.assign({}, inputOpts);
    if (typeof outputOpts.clientHydrateAnnotations !== 'boolean') {
        outputOpts.clientHydrateAnnotations = true;
    }
    if (typeof outputOpts.constrainTimeouts !== 'boolean') {
        outputOpts.constrainTimeouts = true;
    }
    if (typeof outputOpts.maxHydrateCount !== 'number') {
        outputOpts.maxHydrateCount = 300;
    }
    return outputOpts;
}
function generateHydrateResults(opts) {
    if (typeof opts.url !== 'string') {
        opts.url = `https://hydrate.stenciljs.com/`;
    }
    let urlParse;
    try {
        urlParse = new Url(opts.url);
    }
    catch (e) {
        urlParse = {};
    }
    const hydrateResults = {
        diagnostics: [],
        url: opts.url,
        host: urlParse.host,
        hostname: urlParse.hostname,
        port: urlParse.port,
        pathname: urlParse.pathname,
        search: urlParse.search,
        hash: urlParse.hash,
        html: null,
        hydratedCount: 0,
        anchors: opts.collectAnchors ? [] : null,
        components: opts.collectComponents ? [] : null,
        styles: opts.collectScripts ? [] : null,
        scripts: opts.collectStylesheets ? [] : null,
        imgs: opts.collectImgs ? [] : null,
        title: null
    };
    return hydrateResults;
}

async function initializeWindow(results, win, doc, opts) {
    const url = (typeof opts.url === 'string' && opts.url.trim().length > 0) ? opts.url.trim() : '/';
    try {
        const parsedUrl = new URL(url, BASE_URL);
        win.location.href = parsedUrl.href;
    }
    catch (e) {
        catchError(results.diagnostics, e);
    }
    if (typeof opts.userAgent === 'string') {
        try {
            win.navigator.userAgent = opts.userAgent;
        }
        catch (e) { }
    }
    if (typeof opts.cookie === 'string') {
        try {
            doc.cookie = opts.cookie;
        }
        catch (e) { }
    }
    if (typeof opts.referrer === 'string') {
        try {
            doc.referrer = opts.referrer;
        }
        catch (e) { }
    }
    if (typeof opts.direction === 'string') {
        try {
            doc.documentElement.setAttribute('dir', opts.direction);
        }
        catch (e) { }
    }
    if (typeof opts.language === 'string') {
        try {
            doc.documentElement.setAttribute('lang', opts.language);
        }
        catch (e) { }
    }
    if (typeof opts.beforeHydrate === 'function') {
        try {
            await opts.beforeHydrate(win, opts);
        }
        catch (e) {
            catchError(results.diagnostics, e);
        }
    }
    try {
        win.customElements = null;
    }
    catch (e) { }
    try {
        globalScripts(win, true);
    }
    catch (e) {
        catchError(results.diagnostics, e);
    }
    if (opts.constrainTimeouts) {
        constrainTimeouts(win);
    }
}
const BASE_URL = 'http://prerender.stenciljs.com';

function polyfillDocumentImplementation(win, doc) {
    const HTMLElement = doc.documentElement.constructor.prototype;
    if (typeof HTMLElement.getRootNode !== 'function') {
        const elm = doc.createElement('unknown-element');
        const HTMLUnknownElement = elm.constructor.prototype;
        HTMLUnknownElement.getRootNode = getRootNode;
    }
    if (typeof doc.createEvent === 'function') {
        const CustomEvent = doc.createEvent('CustomEvent').constructor;
        if (win.CustomEvent !== CustomEvent) {
            win.CustomEvent = CustomEvent;
        }
    }
}
function getRootNode(opts) {
    const isComposed = (opts != null && opts.composed === true);
    let node = this;
    while (node.parentNode != null) {
        node = node.parentNode;
        if (isComposed === true && node.parentNode == null && node.host != null) {
            node = node.host;
        }
    }
    return node;
}

async function renderToString(html, opts = {}) {
    opts = normalizeHydrateOptions(opts);
    const results = generateHydrateResults(opts);
    if (typeof html !== 'string') {
        const err = buildError(results.diagnostics);
        err.messageText = `Invalid html`;
        return results;
    }
    try {
        const win = new MockWindow(html);
        const doc = win.document;
        await initializeWindow(results, win, doc, opts);
        await new Promise(resolve => {
            const hydratedElements = new Set();
            startRender(opts, results, doc.body, hydratedElements, resolve);
            hydratedElements.clear();
        });
        if (opts.clientHydrateAnnotations) {
            insertVdomAnnotations(doc);
        }
        finalizeWindow(opts, results, win, doc);
        if (results.diagnostics.length === 0) {
            results.html = serializeNodeToHtml(doc, {
                collapseBooleanAttributes: opts.collapseBooleanAttributes,
                pretty: opts.prettyHtml
            });
        }
    }
    catch (e) {
        catchError(results.diagnostics, e);
    }
    return results;
}
async function hydrateDocument(doc, opts = {}) {
    opts = normalizeHydrateOptions(opts);
    const results = generateHydrateResults(opts);
    if (doc == null || doc.nodeType !== 9 || doc.documentElement == null || doc.documentElement.nodeType !== 1) {
        const err = buildError(results.diagnostics);
        err.messageText = `Invalid document`;
        return results;
    }
    try {
        const win = doc.defaultView || new MockWindow(false);
        if (win.document !== doc) {
            win.document = doc;
        }
        if (doc.defaultView !== win) {
            doc.defaultView = win;
        }
        polyfillDocumentImplementation(win, doc);
        await initializeWindow(results, win, doc, opts);
        await new Promise(resolve => {
            const hydratedElements = new Set();
            startRender(opts, results, doc.body, hydratedElements, resolve);
            hydratedElements.clear();
        });
        if (opts.clientHydrateAnnotations) {
            insertVdomAnnotations(doc);
        }
        await finalizeWindow(opts, results, win, doc);
    }
    catch (e) {
        catchError(results.diagnostics, e);
    }
    return results;
}
async function startRender(opts, results, body, hydratedElements, resolve) {
    process.nextTick(async () => {
        const waitPromises1 = [];
        const hydratedElementsBefore = hydratedElements.size;
        try {
            connectElements(opts, results, body, waitPromises1, hydratedElements);
        }
        catch (e) {
            catchError(results.diagnostics, e);
        }
        await Promise.all(waitPromises1);
        process.nextTick(async () => {
            const waitPromises2 = [];
            try {
                connectElements(opts, results, body, waitPromises1, hydratedElements);
            }
            catch (e) {
                catchError(results.diagnostics, e);
            }
            await Promise.all(waitPromises2);
            const hydratedElementsAfter = hydratedElements.size;
            if (hydratedElementsBefore === hydratedElementsAfter) {
                resolve();
            }
            else {
                startRender(opts, results, body, hydratedElements, resolve);
            }
        });
    });
}

function patchNodeGlobal(global) {
    if (typeof global.requestAnimationFrame !== 'function') {
        global.requestAnimationFrame = function (cb) {
            return setTimeout(() => {
                cb(Date.now());
            });
        };
    }
    if (typeof global.cancelAnimationFrame !== 'function') {
        global.cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
    }
    if (typeof global.requestIdleCallback !== 'function') {
        global.requestIdleCallback = function (cb) {
            return setTimeout(() => {
                cb({
                    didTimeout: false,
                    timeRemaining() {
                        return 0;
                    }
                });
            });
        };
    }
    if (typeof global.cancelIdleCallback !== 'function') {
        global.cancelIdleCallback = function (id) {
            clearTimeout(id);
        };
    }
    if (typeof global.matchMedia !== 'function') {
        global.matchMedia = function (media) {
            return {
                matches: false,
                media: media
            };
        };
    }
    if (global.performance == null) {
        global.performance = {
            now() {
                return Date.now();
            }
        };
    }
    if (global.HTMLElement == null) {
        global.HTMLElement = class HTMLElement {
        };
    }
    const WINDOW_FUNCTIONS = [
        'addEventListener',
        'dispatchEvent',
        'removeEventListener'
    ];
    WINDOW_FUNCTIONS.forEach(winKey => {
        patchWindownFunction(global, winKey);
    });
    const WINDOW_OBJECTS = [
        'customElements',
        'document',
        'history',
        'localStorage',
        'location',
        'navigator',
        'sessionStorage',
        'window'
    ];
    WINDOW_OBJECTS.forEach(winKey => {
        patchWindownObject(global, winKey);
    });
}
function patchWindownObject(global, winKey) {
    if (typeof global[winKey] !== 'object') {
        Object.defineProperty(global, winKey, {
            get() {
                console.trace(`Please use a local window reference when using "${winKey}" while prerendering.`);
                return {};
            }
        });
    }
}
function patchWindownFunction(global, winKey) {
    if (typeof global[winKey] !== 'function') {
        global[winKey] = function () {
            console.trace(`Please use a local window reference when using "${winKey}" while prerendering.`);
            return {};
        };
    }
}

const cstrs = new Map();
const loadModule = (cmpMeta, _hostRef, _hmrVersionId) => {
    return new Promise(resolve => {
        resolve(cstrs.get(cmpMeta.$tagName$));
    });
};
const getComponent = (tagName) => {
    return cstrs.get(tagName);
};
const registerComponents = (Cstrs) => {
    Cstrs.forEach(Cstr => {
        cstrs.set(Cstr.cmpMeta.$tagName$, Cstr);
    });
};
const getDoc = (elm) => {
    if (elm != null) {
        if (elm.nodeType === 9) {
            return elm;
        }
        return elm.ownerDocument;
    }
    return null;
};
const getWin = (elm) => {
    const doc = getDoc(elm);
    if (doc != null) {
        return doc.defaultView;
    }
    return null;
};
const getHead = (elm) => {
    const doc = getDoc(elm);
    if (doc != null) {
        return doc.head;
    }
    return null;
};
const readTask = (cb) => {
    process.nextTick(cb);
};
const writeTask = (cb) => {
    process.nextTick(cb);
};
const tick = Promise.resolve();
const consoleError = (e) => {
    if (e != null) {
        console.error(e.stack || e.message || e);
    }
};
const Context = {
    isServer: true,
    enableListener: () => console.log('TODO'),
    queue: {
        write: writeTask,
        read: readTask,
        tick
    }
};
const getContext = (ref, context) => {
    if (context === 'window') {
        return getWin(getHostRef(ref).$hostElement$);
    }
    if (context === 'document') {
        return getDoc(getHostRef(ref).$hostElement$);
    }
    if (context === 'isServer') {
        return true;
    }
    return Context[context];
};
const plt = {};
const supportsShadowDom = false;
const supportsListenerOptions = false;
const supportsConstructibleStylesheets = false;
const hostRefs = new WeakMap();
const getHostRef = (ref) => hostRefs.get(ref);
const registerInstance = (lazyInstance, hostRef) => hostRefs.set(hostRef.$lazyInstance$ = lazyInstance, hostRef);
const registerHost = (elm) => {
    const hostRef = {
        $stateFlags$: 0,
        $hostElement$: elm,
        $instanceValues$: new Map(),
    };
    hostRef.$onReadyPromise$ = new Promise(r => hostRef.$onReadyResolve$ = r);
    return hostRefs.set(elm, hostRef);
};
const styles = new Map();

export { consoleError, getComponent, getContext, getDoc, getHead, getHostRef, getWin, hydrateDocument, loadModule, patchNodeGlobal, plt, readTask, registerComponents, registerHost, registerInstance, renderToString, styles, supportsConstructibleStylesheets, supportsListenerOptions, supportsShadowDom, tick, writeTask };
