"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_STYLE = void 0;
const classnames_1 = require("../../../../../client/classnames");
const getDatumSize_1 = require("../../../../../client/component/getDatumSize");
const mergeStyle_1 = require("../../../../../client/component/mergeStyle");
const element_1 = require("../../../../../client/element");
const parentElement_1 = require("../../../../../client/platform/web/parentElement");
const theme_1 = require("../../../../../client/theme");
const idFromUnitValue_1 = require("../../../../../spec/idFromUnitValue");
const parser_1 = require("../../../../../spec/parser");
const Component_1 = require("../../Div/Component");
const Component_2 = require("../DataTreeLeaf/Component");
const STYLE_SEPARATOR = {
    display: 'flex',
    alignItems: 'center',
    textAlign: 'center',
    height: `${getDatumSize_1.LEAF_HEIGHT}px`,
    width: '6px',
    fontSize: '12px',
    lineHeight: `${getDatumSize_1.LEAF_HEIGHT}px`,
    boxSizing: 'border-box',
};
const STYLE_DELIMITER = {
    display: 'flex',
    alignItems: 'center',
    textAlign: 'center',
    height: `${getDatumSize_1.LEAF_HEIGHT}px`,
    width: '6px',
    fontSize: '12px',
    lineHeight: `${getDatumSize_1.LEAF_HEIGHT}px`,
    boxSizing: 'border-box',
};
const STYLE_PARENT = (overflow) => {
    return {
        display: 'flex',
        // textAlign: 'left',
        height: 'fit-content',
        width: 'fit-content',
        flexDirection: overflow ? 'column' : 'row',
    };
};
const STYLE_CONTAINER = (overflow) => {
    return {
        display: 'flex',
        color: 'currentcolor',
        boxSizing: 'border-box',
        flexDirection: overflow ? 'column' : 'row',
        marginLeft: overflow
            ? `${getDatumSize_1.CONTAINER_COLUMN_LEFT_MARGIN}px`
            : `${getDatumSize_1.CONTAINER_ROW_LEFT_MARGIN}px`,
        marginRight: overflow
            ? `${getDatumSize_1.CONTAINER_COLUMN_RIGHT_MARGIN}px`
            : `${getDatumSize_1.CONTAINER_ROW_RIGHT_MARGIN}px`,
        height: 'fit-content',
        width: 'fit-content',
    };
};
const STYLE_COMMA = {
    display: 'flex',
    height: `${getDatumSize_1.LEAF_HEIGHT}px`,
    width: '6px',
    textAlign: 'left',
    fontSize: '12px',
    boxSizing: 'border-box',
};
const STYLE_SPACE = {
    display: 'inline-block',
    width: '2px',
    boxSizing: 'border-box',
};
exports.DEFAULT_STYLE = {
    width: 'fit-content',
    height: '100%',
    color: 'currentcolor',
    boxSizing: 'border-box',
};
class DataTree extends element_1.Element {
    constructor($props, $system) {
        super($props, $system);
        this._child = {};
        this._leaf = null;
        this.__primitive = (data) => this._primitive(data);
        this.__key_value = (data) => this._key_value(data);
        this.__array_expression = (data) => this._array_expression(data);
        this.__unit = (data) => this._unit(data);
        this.__object_expression = (data) => this._object_expression(data);
        this.__expression = (data) => this._expression(data);
        this.__object_literal = (data) => this._object_literal(data);
        this.__array_literal = (data) => this._array_literal(data);
        this.__and = (data) => this._and(data);
        this.__or = (data) => this._or(data);
        this.NODE_TYPE_TO_ELEMENT = {
            [parser_1.TreeNodeType.Any]: this.__primitive,
            [parser_1.TreeNodeType.Invalid]: this.__primitive,
            [parser_1.TreeNodeType.Generic]: this.__primitive,
            [parser_1.TreeNodeType.Null]: this.__primitive,
            [parser_1.TreeNodeType.Identifier]: this.__primitive,
            [parser_1.TreeNodeType.Object]: this.__primitive,
            [parser_1.TreeNodeType.String]: this.__primitive,
            [parser_1.TreeNodeType.Number]: this.__primitive,
            [parser_1.TreeNodeType.Boolean]: this.__primitive,
            [parser_1.TreeNodeType.StringLiteral]: this.__primitive,
            [parser_1.TreeNodeType.BooleanLiteral]: this.__primitive,
            [parser_1.TreeNodeType.NumberLiteral]: this.__primitive,
            [parser_1.TreeNodeType.Regex]: this.__primitive,
            [parser_1.TreeNodeType.RegexLiteral]: this.__primitive,
            [parser_1.TreeNodeType.Unit]: this.__unit,
            [parser_1.TreeNodeType.Class]: this.__primitive,
            [parser_1.TreeNodeType.ClassLiteral]: this.__primitive,
            [parser_1.TreeNodeType.ArithmeticExpression]: this.__primitive,
            [parser_1.TreeNodeType.PropExpression]: this.__primitive,
            [parser_1.TreeNodeType.KeyValue]: this.__key_value,
            [parser_1.TreeNodeType.ArrayExpression]: this.__array_expression,
            [parser_1.TreeNodeType.ObjectExpression]: this.__object_expression,
            [parser_1.TreeNodeType.Expression]: this.__expression,
            [parser_1.TreeNodeType.ObjectLiteral]: this.__object_literal,
            [parser_1.TreeNodeType.ArrayLiteral]: this.__array_literal,
            [parser_1.TreeNodeType.And]: this.__and,
            [parser_1.TreeNodeType.Or]: this.__or,
        };
        this._reset = () => {
            // console.log('DataTree', 'reset')
            var _a;
            const { data, style: _style, invalid } = this.$props;
            this._data = data;
            this._child = {};
            const element = invalid
                ? this.__primitive(data)
                : ((_a = this.NODE_TYPE_TO_ELEMENT[data.type]) !== null && _a !== void 0 ? _a : this.__primitive)(data);
            const { style, children } = element;
            this._root.setProp('style', Object.assign(Object.assign(Object.assign({}, exports.DEFAULT_STYLE), style), _style));
            this._root.setChildren(children);
        };
        this._child_element = (index, data, appendChildren, className, style, invalid) => {
            const { path = [], data: parent_data } = this.$props;
            const child = new DataTree({
                className,
                style,
                data,
                invalid,
                path: [...path, index],
                parent: parent_data,
                appendChildren,
            }, this.$system);
            this._child[index] = child;
            return child;
        };
        this._object_literal = (data) => {
            const { appendChildren = [], parent } = this.$props;
            const empty = data.children.length === 0;
            const overflow = (0, getDatumSize_1.childrenOverflow)(data);
            const style = STYLE_PARENT(overflow);
            const object_literal_open_delimiter = this._delimiter({
                display: overflow && parent && parent.type === parser_1.TreeNodeType.KeyValue
                    ? 'none'
                    : 'flex',
            }, '{');
            const object_literal_container = new Component_1.default({
                className: 'object-literal-container',
                style: STYLE_CONTAINER(overflow),
            }, this.$system);
            for (let i = 0; i < data.children.length; i++) {
                const child = data.children[i];
                const comma = this._comma();
                const space = this._space();
                if (child.type === parser_1.TreeNodeType.KeyValue) {
                    const key_value = child;
                    const key_value_tree = this._child_element(i, key_value, i < data.children.length - 1 ? [comma, space] : [], 'comma-space');
                    object_literal_container.appendChild(key_value_tree);
                }
                else if ((0, parser_1._isValidObjKeyType)(child)) {
                    const key = child;
                    const key_tree = this._child_element(i, key, i < data.children.length - 1 ? [comma, space] : [], 'comma-space');
                    (0, mergeStyle_1.default)(key_tree, { display: 'flex' });
                    object_literal_container.appendChild(key_tree);
                }
                else if (child.value === '') {
                    const empty_tree = this._child_element(i, child, i < data.children.length - 1 ? [comma, space] : []);
                    object_literal_container.appendChild(empty_tree);
                }
                else {
                    const invalid_tree = this._child_element(i, child, i < data.children.length - 1 ? [comma, space] : []);
                    object_literal_container.appendChild(invalid_tree);
                }
            }
            if (empty) {
                const empty_tree = this._child_element(0, (0, parser_1.getTree)(''));
                object_literal_container.appendChild(empty_tree);
            }
            const object_literal_close_delimiter = this._delimiter({}, `}`);
            const object_literal_end = new Component_1.default({
                className: 'object-literal',
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    height: '100%',
                    color: 'currentcolor',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            object_literal_end.setChildren([
                object_literal_close_delimiter,
                ...appendChildren,
            ]);
            const children = [
                object_literal_open_delimiter,
                object_literal_container,
                object_literal_end,
            ];
            return { style, children };
        };
        this._array_literal = (data) => {
            const { appendChildren = [], parent } = this.$props;
            const empty = data.children.length === 0;
            const overflow = (0, getDatumSize_1.childrenOverflow)(data);
            const style = STYLE_PARENT(overflow);
            const array_literal_open_delimiter = this._delimiter({
                display: overflow && parent && parent.type === parser_1.TreeNodeType.KeyValue
                    ? 'none'
                    : 'flex',
            }, '[');
            const array_literal_container = new Component_1.default({
                className: 'array-literal-container',
                style: Object.assign({}, STYLE_CONTAINER(overflow)),
            }, this.$system);
            for (let i = 0; i < data.children.length; i++) {
                const element = data.children[i];
                const comma = this._comma();
                const space = this._space();
                const array_literal_element_tree = this._child_element(i, element, i < data.children.length - 1 ? [comma, space] : []);
                array_literal_container.appendChild(array_literal_element_tree);
            }
            if (empty) {
                const array_literal_empty_tree = this._child_element(0, (0, parser_1.getTree)(''), [], 'key-value-value-tree');
                array_literal_container.appendChild(array_literal_empty_tree);
            }
            const array_literal_close_delimiter = this._delimiter({}, `]`);
            const array_literal_end = new Component_1.default({
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    color: 'currentcolor',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            array_literal_end.setChildren([
                array_literal_close_delimiter,
                ...appendChildren,
            ]);
            const children = [
                array_literal_open_delimiter,
                array_literal_container,
                array_literal_end,
            ];
            return { style, children };
        };
        this._expression = (data) => {
            const { path = [], appendChildren = [] } = this.$props;
            const empty = data.children.length === 0;
            const overflow = (0, getDatumSize_1.childrenOverflow)(data);
            const style = STYLE_PARENT(overflow);
            const expression_open_delimiter = this._delimiter({ display: overflow && path[path.length - 1] > 0 ? 'none' : 'block' }, '(');
            const expression_container = new Component_1.default({
                style: Object.assign({ className: 'expression-container' }, STYLE_CONTAINER(overflow)),
            }, this.$system);
            for (let i = 0; i < data.children.length; i++) {
                const element = data.children[i];
                const comma = this._comma();
                const space = this._space();
                const expression_element_tree = this._child_element(i, element, i < data.children.length - 1 ? [comma, space] : []);
                expression_container.appendChild(expression_element_tree);
            }
            if (empty) {
                const array_literal_empty_tree = this._child_element(0, (0, parser_1.getTree)(''), [], 'expression');
                expression_container.appendChild(array_literal_empty_tree);
            }
            const expression_close_delimiter = this._delimiter({}, `)`);
            const expression_literal_end = new Component_1.default({
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    color: 'currentcolor',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            expression_literal_end.setChildren([
                expression_close_delimiter,
                ...appendChildren,
            ]);
            const children = [
                expression_open_delimiter,
                expression_container,
                expression_literal_end,
            ];
            return { style, children };
        };
        this._key_value = (data) => {
            const { appendChildren = [] } = this.$props;
            const value_tree = data.children[1];
            const key_tree = data.children[0];
            const value_overflow = (0, getDatumSize_1.childrenOverflow)(value_tree);
            const style = {
                display: 'flex',
                flexDirection: value_overflow ? 'column' : 'row',
            };
            const key_value_key = new Component_1.default({
                className: 'key-value-key',
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    color: 'currentcolor',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            const is_invalid_key = !(0, parser_1.isValidObjKey)(key_tree.value);
            const key_value_key_tree = this._child_element(0, key_tree, [], 'key-value-key-tree', {}, is_invalid_key);
            key_value_key.appendChild(key_value_key_tree);
            if (value_overflow) {
                const key_value_overflow_colon = this._delimiter({}, ':');
                const key_value_overflow_space = this._space();
                key_value_key.appendChild(key_value_overflow_colon);
                key_value_key.appendChild(key_value_overflow_space);
                if (value_tree.type === parser_1.TreeNodeType.ObjectLiteral) {
                    const key_value_key_delimiter = this._delimiter({}, '{');
                    key_value_key.appendChild(key_value_key_delimiter);
                }
                else if (value_tree.type === parser_1.TreeNodeType.ArrayLiteral) {
                    const key_value_key_delimiter = this._delimiter({}, '[');
                    key_value_key.appendChild(key_value_key_delimiter);
                }
                else if (value_tree.type === parser_1.TreeNodeType.Expression) {
                    const key_value_key_delimiter = this._delimiter({}, '(');
                    key_value_key.appendChild(key_value_key_delimiter);
                }
            }
            const key_value_colon = this._delimiter({ display: value_overflow ? 'none' : 'block' }, ':');
            const key_value_space = this._space();
            const key_value_value = new Component_1.default({
                className: 'key-value-value',
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            const key_value_value_tree = this._child_element(1, value_tree, appendChildren, 'key-value-value-tree');
            key_value_value.appendChild(key_value_value_tree);
            const children = [
                key_value_key,
                key_value_colon,
                key_value_space,
                key_value_value,
            ];
            return { style, children };
        };
        this._primitive = (data) => {
            const { path = [], parent, appendChildren = [] } = this.$props;
            const style = { display: 'flex' };
            this._leaf = new Component_2.default({
                style: {
                    padding: '0',
                    textAlign: 'center',
                    resize: 'none',
                    height: `${getDatumSize_1.LEAF_HEIGHT}px`,
                    maxWidth: '210px',
                    fontWeight: 'inherit',
                    wordWrap: 'normal',
                    whiteSpace: 'nowrap',
                    background: theme_1.COLOR_NONE,
                    backgroundColor: theme_1.COLOR_NONE,
                },
                value: data.value,
                path,
                parent,
            }, this.$system);
            const children = [this._leaf, ...appendChildren];
            return { style, children };
        };
        this._unit = (data) => {
            const { specs, classes } = this.$system;
            const id = (0, idFromUnitValue_1.idFromUnitValue)(data.value, specs, classes);
            const _data = (0, parser_1.getTree)(`\`${id}\``);
            return this._primitive(_data);
        };
        this._array_expression = (data) => {
            const { appendChildren = [] } = this.$props;
            const style = { display: 'flex', width: 'fit-content' };
            const array_expression_open_delimiter = this._delimiter({}, '[');
            const array_expression_close_delimiter = this._delimiter({}, ']');
            const array_expression_open_close = new Component_1.default({
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            array_expression_open_close.setChildren([
                array_expression_open_delimiter,
                array_expression_close_delimiter,
                ...appendChildren,
            ]);
            const array_expression_tree = this._child_element(0, data.children[0], [array_expression_open_close], 'array-expression');
            const children = [array_expression_tree];
            return { style, children };
        };
        this._object_expression = (data) => {
            const {} = this.$props;
            const style = { display: 'flex' };
            const object_expression_open_delimiter = this._delimiter({}, '{');
            const object_expression_close_delimiter = this._delimiter({}, '}');
            const object_expression_open_close = new Component_1.default({
                style: {
                    display: 'flex',
                    width: 'fit-content',
                    boxSizing: 'border-box',
                },
            }, this.$system);
            object_expression_open_close.setChildren([
                object_expression_open_delimiter,
                object_expression_close_delimiter,
            ]);
            const object_expression_tree = this._child_element(0, data.children[0], [object_expression_open_close], 'object-expression');
            const children = [object_expression_tree];
            return { style, children };
        };
        this._or = (data) => {
            const {} = this.$props;
            const overflow = (0, getDatumSize_1.childrenOverflow)(data);
            const style = Object.assign(Object.assign({}, STYLE_PARENT(overflow)), { alignItems: 'center' });
            const children = [];
            for (let i = 0; i < data.children.length; i++) {
                const element = data.children[i];
                const or_delimiter = this._separator({}, '|');
                const or_element_tree = this._child_element(i, element, i < data.children.length - 1 ? [or_delimiter] : [], 'or');
                children.push(or_element_tree);
            }
            return { style, children };
        };
        this._and = (data) => {
            const {} = this.$props;
            const style = { display: 'flex', alignItems: 'center' };
            const children = [];
            for (let i = 0; i < data.children.length; i++) {
                const element = data.children[i];
                const and_delimiter = this._separator({}, '&');
                const and_element_tree = this._child_element(i, element, i < data.children.length - 1 ? [and_delimiter] : [], 'and', { alignItems: 'center' });
                children.push(and_element_tree);
            }
            return { style, children };
        };
        this._space = () => {
            return new Component_1.default({
                style: Object.assign({}, STYLE_SPACE),
                innerText: ' ',
            }, this.$system);
        };
        this._comma = () => {
            return new Component_1.default({
                style: Object.assign({}, STYLE_COMMA),
                innerText: ',',
            }, this.$system);
        };
        this._separator = (style, innerText) => {
            return new Component_1.default({
                className: 'separator',
                style: Object.assign(Object.assign({}, STYLE_SEPARATOR), style),
                innerText,
            }, this.$system);
        };
        this._delimiter = (style, innerText) => {
            return new Component_1.default({
                className: 'delimiter',
                style: Object.assign(Object.assign({}, STYLE_DELIMITER), style),
                innerText,
            }, this.$system);
        };
        this.focus = (options = { preventScroll: true }) => {
            if (this._leaf) {
                this._leaf.focus(options);
            }
        };
        this.blur = () => {
            if (this._leaf) {
                this._leaf.blur();
            }
        };
        this.getSelectionRange = () => {
            if (this._leaf) {
                // TODO `leaf` should `extend` input
                const start = this._leaf._input.$element.selectionStart || 0;
                const end = this._leaf._input.$element.selectionEnd || 0;
                return { start, end };
            }
            return { start: 0, end: 0 };
        };
        this.setSelectionRange = (start, end, direction) => {
            if (this._leaf) {
                this._leaf.setSelectionRange(start, end, direction);
            }
        };
        this.focusLeaf = (path, options) => {
            const child = this.getChildAtPath(path);
            if (child) {
                child.focus(options);
            }
        };
        this.blurLeaf = (path) => {
            const child = this.getChildAtPath(path);
            if (child) {
                child.blur();
            }
        };
        this.getChild = (index) => {
            return this._child[index] || null;
        };
        this.getChildAtPath = (path) => {
            let child = this;
            for (const index of path) {
                if (child) {
                    child = child.getChild(index);
                }
                else {
                    throw new Error(`there is no child at ${path}`);
                }
            }
            return child;
        };
        const { className, style } = $props;
        const root = new Component_1.default({
            className: (0, classnames_1.default)('root', className),
            style: Object.assign(Object.assign({}, exports.DEFAULT_STYLE), style),
        }, this.$system);
        this._root = root;
        this._reset();
        const $element = (0, parentElement_1.default)($system);
        this.$element = $element;
        this.$slot = {
            default: root,
        };
        this.setSubComponents({
            root,
        });
        this.registerRoot(root);
    }
    update(data) {
        // console.log('DataTree', 'update', data)
        if (data.type === this._data.type) {
            if ((0, parser_1.isCompositeType)(data.type)) {
                const l = data.children.length;
                const _l = this._data.children.length;
                const L = Math.max(l, _l);
                for (let i = 0; i < L; i++) {
                    const _child = this._data.children[i];
                    const child = data.children[i];
                    if (_child && child) {
                        const _child_comp = this._root.$children[i];
                        _child_comp.update(child);
                    }
                    else if (_child && !child) {
                        for (let j = _l - 1; j >= i; j--) {
                            this._root.removeChildAt(j);
                        }
                        break;
                    }
                    else if (!_child && child) {
                        // const child_comp = new DataTree({ data: child })
                        // this._root.appendChild(child_comp)
                        const { style, children } = this.NODE_TYPE_TO_ELEMENT[child.type](child);
                        for (const child of children) {
                            this._root.appendChild(child);
                        }
                    }
                    else {
                        throw new Error('something impossible just happened');
                    }
                }
            }
            else {
                this._leaf.setProp('value', data.value);
            }
        }
        else {
            this._data = data;
            this._reset();
        }
    }
    onPropChanged(prop, current) {
        if (prop === 'data') {
            this._reset();
            // this.update(current || EMPTY_TREE)
        }
        else if (prop === 'style') {
            this._root.setProp('style', Object.assign(Object.assign({}, exports.DEFAULT_STYLE), current));
        }
    }
    setLeafSelectionRange(path, start, end, direction) {
        const child = this.getChildAtPath(path);
        if (child) {
            child.setSelectionRange(start, end, direction);
        }
    }
}
exports.default = DataTree;
//# sourceMappingURL=Component.js.map