"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * @module fly
 * @private
 */
const htmlparser = require("htmlparser2");
const domutils_1 = require("domutils");
const logger_1 = require("./logger");
const WritableParser = htmlparser.WritableStream;
const css = require("css-select");
class Node {
    constructor(dom) {
        this._dom = dom;
    }
    querySelector(selector) {
        let found = css.selectOne(selector, this._dom);
        if (!found)
            return null;
        return new Element(this._withParent(found));
    }
    querySelectorAll(selector) {
        return css.selectAll(selector, this._dom)
            .map(d => new Element(this._withParent(d)));
    }
    _withParent(node) {
        node.parent || (node.parent = this);
        return node;
    }
    set children(children) {
        this._dom = children;
    }
    get children() {
        return this._dom;
    }
    appendChild(html) {
        if (typeof html._dom !== 'undefined') {
            // Document
            if (Array.isArray(this._dom))
                domutils_1.appendChild(this._dom[1], html._dom);
            else
                domutils_1.appendChild(this._dom, html._dom);
            return html;
        }
        html = new Element(parseDOMSync(html)[0]);
        return this.appendChild(html);
    }
}
exports.Node = Node;
class Document extends Node {
    constructor(dom) {
        super(dom);
    }
    getElementById(id) {
        return this.querySelector(`#${id}`);
    }
    get documentElement() {
        return new Element(this._dom);
    }
    createElement(tagName) {
        return new Element(parseDOMSync(`<${tagName}></${tagName}>`)[0]);
    }
    static parse(html) {
        return new Document(parseDOMSync(html));
    }
}
exports.Document = Document;
class Element extends Node {
    constructor(dom) {
        super(dom);
    }
    get id() {
        return this._dom.id;
    }
    get textContent() {
        return domutils_1.getText(this._dom);
    }
    get innerHTML() {
        return domutils_1.getInnerHTML(this._dom);
    }
    get outerHTML() {
        return domutils_1.getOuterHTML(this._dom);
    }
    replaceWith(html) {
        if (html instanceof Element) {
            domutils_1.replaceElement(this._dom, html._dom);
            return;
        }
        domutils_1.replaceElement(this._dom, parseDOMSync(html)[0]);
    }
    getAttribute(name) {
        return domutils_1.getAttributeValue(this._dom, name);
    }
    setAttribute(name, value) {
        this._dom.attribs[name] = value;
    }
}
exports.Element = Element;
class DocumentParser {
    constructor() {
        this.parser = parseDOMStreaming(this.onElement.bind(this));
        this.selectors = [];
    }
    querySelector(selector, callback) {
        this.selectors.push({
            fn: css.compile(selector),
            callback
        });
    }
    onElement(elem) {
        let found = this.selectors.find((s) => s.fn(elem));
        if (found)
            found.callback(new Element(elem));
    }
    async parse(stream) {
        let fullyRead = false;
        if (stream instanceof ReadableStream)
            stream = stream.getReader();
        else
            return this.parseSync(stream);
        while (!fullyRead) {
            const { done, value } = await stream.read();
            if (done) {
                fullyRead = true;
                break;
            }
            this.parser.write(value);
        }
        this.parser.end();
    }
    parseSync(arg) {
        // TODO: this method wasn't declared prior to the move to TS but referred from the parse
        // method if stream is a string. This might be an overlooked bug, will fix once we know
        // the desired behavior
        throw new Error("This wasn't a method before TS, wtf?");
    }
}
Document.Parser = DocumentParser;
function parseDOMSync(html) {
    let handler = new htmlparser.DomHandler();
    new htmlparser.Parser(handler)
        .end(html);
    return handler.dom;
}
function parseDOMStreaming(elemCb) {
    let handler = new htmlparser.DomHandler(() => {
        logger_1.logger.debug("done parsing dom");
    }, undefined, (elem) => {
        logger_1.logger.debug("got an element!", elem.attribs);
        elemCb(elem);
    });
    return new WritableParser(handler);
}
//# sourceMappingURL=document.js.map