"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var htmlStructure_1 = require("./htmlStructure");
var Helper = require("./helper");
var CoreLogic = require("./coreLogic");
var Leaf = require("../../build/leaf.export");
var fs = require("fs");
var pathModule = require("path");
//https://github.com/vvakame/typescript-formatter/blob/master/lib/formatter.ts
var formatTypescript = require("typescript-formatter/lib/formatter");
var Context = (function () {
    function Context(path, debug) {
        if (debug === void 0) { debug = false; }
        this.path = path;
        this.debug = debug;
        this.widgets = [];
        this.rootName = "R";
    }
    Context.prototype.includeWidgetByRelativeFilePath = function (path) {
        var content = fs.readFileSync(pathModule.join(this.path, path), "utf8");
        var tags = htmlStructure_1.parseHTMLStructure(content);
        var ext = pathModule.extname(path);
        var base = pathModule.basename(path, ext);
        var dir = pathModule.dirname(path);
        var fullBase = pathModule.join(dir, base);
        if (fullBase[0] != "/")
            fullBase = "/" + fullBase;
        this.widgets.push(new WidgetTemplate(this, tags, fullBase));
    };
    Context.prototype.compile = function () {
        // 1. build widgets into tree
        // 2. recursively generate widget from tree
        var tree = this.buildTree(this.widgets);
        var result = this.recursiveGenerate(tree);
        var widgetPathes = this.getAllWidgetPath(this.widgets);
        var extra = "let compiledAt = \"" + new Date().toString() + "\"" && "";
        return formatTypescript.default("R.ts", "namespace " + this.rootName + "{\n" + Helper.indentForward(result) + ("\n" + extra + "\nexport const widgets = " + JSON.stringify(widgetPathes) + "\n\n}") + ("\nwindow[\"" + this.rootName + "\"] = " + this.rootName + "\n"));
    };
    Context.prototype.compileStyle = function () {
        return this.widgets.map(function (widget) { return widget.generateStyle(); }).filter(function (item) { return !!item; }).join("\n");
    };
    Context.prototype.getAllWidgetPath = function (widgets) {
        var result = [];
        for (var _i = 0, widgets_1 = widgets; _i < widgets_1.length; _i++) {
            var child = widgets_1[_i];
            result.push.apply(result, child.getAllWidgetPath());
        }
        return result;
    };
    Context.prototype.buildTree = function (widgets) {
        var root = {};
        for (var _i = 0, widgets_2 = widgets; _i < widgets_2.length; _i++) {
            var widget = widgets_2[_i];
            var names = widget.path.split("/").filter(function (item) { return !!item; });
            var name_1 = names.pop();
            var cur = root;
            for (var _a = 0, names_1 = names; _a < names_1.length; _a++) {
                var folder = names_1[_a];
                if (root[folder])
                    cur = root[folder];
                else
                    cur = root[folder] = {};
                if (cur instanceof WidgetTemplate) {
                    throw new Error("Widget shouldn't have name confliction the the same level folder:" + folder);
                }
            }
            cur[name_1] = widget;
        }
        return root;
    };
    Context.prototype.recursiveGenerate = function (tree) {
        var result = "";
        for (var prop in tree) {
            var sub = tree[prop];
            if (sub instanceof WidgetTemplate) {
                result += sub.compile({ debug: this.debug });
            }
            else {
                var name_2 = prop;
                name_2 = Leaf.Util.capitalize(name_2);
                result += "export namespace " + name_2 + "{";
                result += Helper.indentForward(this.recursiveGenerate(sub));
                result += "}";
            }
        }
        return result;
    };
    Context.prototype.generateNamespace = function (t, holder) {
        if (holder === void 0) { holder = "WidgetContent"; }
        // nested namespace
        var parts = pathModule.normalize(pathModule.dirname(t.path)).split("/").map(function (item) { return item.trim(); }).filter(function (item) { return !!item; });
        var n;
        var result = "";
        while (n = parts.pop()) {
            result = "export namespace " + n + "{\n" + Helper.indentForward(result) + " \n}";
        }
        return result;
    };
    return Context;
}());
exports.Context = Context;
var WidgetTemplate = (function () {
    function WidgetTemplate(context, templateNodes, path) {
        this.context = context;
        this.templateNodes = templateNodes;
        this.path = path;
        this.children = [];
        this.parent = null;
        this.id = null;
        this.debugContext = null;
        this.widgetCompileTemplate = "\nexport class {{WidgetName}} extends Leaf.GeneratedWidget<{{UIInterfaces}},{{VMInterfaces}},{{VMStringLiterals}}>{\n    constructor() {\n        super({{Template}}, \"{{WidgetName}}\",{{VMStringArray}})\n        if({{WidgetName}}.InitialData){\n            this.renderRecursive({{WidgetName}}.InitialData)\n        }\n    }\n    {{ListsDeclarations}}\n    {{WidgetsDeclarations}}\n    static TestDatas = {{TestDatas}}\n    static BindedLists = {{BindedLists}}\n    static BindedWidgets = {{BindedWidgets}}\n    static path = {{Path}}\n    static InitialData = {{InitialData}}\n    public TestDatas = {{WidgetName}}.TestDatas\n    public BindedLists = {{WidgetName}}.BindedLists\n    public BindedWidgets = {{WidgetName}}.BindedWidgets\n    public InitialData = {{WidgetName}}.InitialData\n    public path = {{WidgetName}}.path\n    public GeneratedConstructor = {{WidgetName}}\n    public R = {{RootName}}\n}\n({{WidgetName}}[\"prototype\"] as any).name = \"{{WidgetName}}\"\n";
        this.name = pathModule.basename(path);
        this.debugContext = {
            name: this.name,
            file: this.path,
            path: this.path
        };
        this.parser = new WidgetStructureParser(templateNodes, path, this.debugContext);
        this.id = CoreLogic.pathToId(this.path);
        for (var _i = 0, _a = this.parser.templates; _i < _a.length; _i++) {
            var template = _a[_i];
            if (!template.name) {
                this.error("template should have a name");
                continue;
            }
            this.addChild(template.tag.children, template.name);
        }
    }
    WidgetTemplate.prototype.error = function (arg) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        console.error.apply(console, [arg].concat(args));
    };
    WidgetTemplate.prototype.getAllWidgetPath = function () {
        var path = [this.path];
        for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
            var child = _a[_i];
            path.push.apply(path, child.getAllWidgetPath());
        }
        return path;
    };
    WidgetTemplate.prototype.addChild = function (tags, name) {
        var w = new WidgetTemplate(this.context, tags, pathModule.join(this.path, name));
        w.parent = this;
        this.children.push(w);
    };
    WidgetTemplate.prototype.generateStyle = function () {
        var childStyles = this.children.map(function (item) { return item.generateStyle(); }).filter(function (item) { return !!item; }).join("\n");
        if (this.parser.styles.length === 0 && childStyles.length === 0)
            return "";
        var widgetStyle = "";
        if (this.parser.styles.length > 0) {
            widgetStyle = htmlStructure_1.structureToString(this.parser.styles[0]);
        }
        // child style should be at same level with parent, since every class are generated by a hash, so it's unlikely
        // to conflict. And if we don't nested we can preview the child alone
        return "." + this.id + "{\n" + Helper.indentForward(widgetStyle) + "\n}\n" + childStyles + "\n";
    };
    WidgetTemplate.prototype.compile = function (option) {
        var _this = this;
        // 1. compile myself.
        // 2. compile children and put them into my namespace
        var WidgetName = Leaf.Util.capitalize(this.name);
        var UIInterfaces = "{" +
            this.parser.UIs.map(function (item) { return Leaf.Util.slugToCamel(item.name) + ":" + CoreLogic.getElementTypeStringByTagName(item.tagName); }).join(",")
            + "}";
        var VMInterfaces = "{" + this.parser.VMs.map(function (item) { return item + ":any"; }).join(",") + "}";
        var VMStringLiterals = this.parser.VMs.map(function (item) { return "\"" + item + "\""; }).join("|") || "null";
        var Template = this.parser.getTemplateFunctionString();
        var VMStringArray = "[" + this.parser.VMs.map(function (item) { return "\"" + item + "\""; }).join(",") + "]";
        var ListsDeclarations = this.parser.lists.map(function (list) {
            if (list.reference)
                return list.name + ":Leaf.List<" + _this.context.rootName + "." + CoreLogic.pathToResourceResolution(list.reference) + ">";
            return list.name + ":Leaf.List<Leaf.WidgetAny>";
        }).join("\n");
        var WidgetsDeclarations = this.parser.widgets.filter(function (widget) { return !!widget.name; }).map(function (widget) {
            if (widget.path) {
                return widget.name + ":" + _this.context.rootName + "." + CoreLogic.pathToResourceResolution(widget.path);
            }
            return widget.name + ":Leaf.WidgetAny";
        }).join("\n");
        var Path = "\"" + this.path + "\"";
        var TestDatas = JSON.stringify(this.parser.testDatas);
        var InitialData = JSON.stringify(this.parser.initialData);
        var BindedLists = JSON.stringify(this.parser.lists);
        var BindedWidgets = JSON.stringify(this.parser.widgets);
        var RootName = this.context.rootName;
        var self = Leaf.render(this.widgetCompileTemplate, {
            WidgetName: WidgetName, UIInterfaces: UIInterfaces, VMInterfaces: VMInterfaces, VMStringLiterals: VMStringLiterals, Template: Template, VMStringArray: VMStringArray, ListsDeclarations: ListsDeclarations, WidgetsDeclarations: WidgetsDeclarations, Path: Path, TestDatas: TestDatas, BindedLists: BindedLists, BindedWidgets: BindedWidgets, InitialData: InitialData, RootName: RootName
        });
        if (this.children.length > 0) {
            self = self + ("export namespace " + WidgetName + "{")
                + Helper.indentForward(this.children.map(function (item) { return item.compile(); }).join("\n"))
                + "}";
        }
        return self;
    };
    return WidgetTemplate;
}());
exports.WidgetTemplate = WidgetTemplate;
var WidgetStructureParserDesign = (function () {
    function WidgetStructureParserDesign(tags, path, debugContext) {
        this.tags = tags;
        this.path = path;
        this.debugContext = debugContext;
        this.templates = [];
        this.widgets = [];
        this.UIs = [];
        this.VMs = [];
        this.slots = [];
        this.lists = [];
        this.testDatas = [];
        this.initialData = null;
        this.scripts = [];
        this.styles = [];
        this.templateNodes = [];
        this.rootWidgetNode = null;
        this.parse(tags);
    }
    return WidgetStructureParserDesign;
}());
var WidgetStructureParser = (function (_super) {
    __extends(WidgetStructureParser, _super);
    function WidgetStructureParser() {
        return _super.apply(this, arguments) || this;
    }
    WidgetStructureParser.prototype.error = function (arg) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        console.error.apply(console, [this.debugContext.file, ":", arg].concat(args));
    };
    WidgetStructureParser.prototype.getTemplateFunctionString = function () {
        return "function(){\n    let _e = this._e.bind(this)\n    let _t = this._t.bind(this)\n    return " + this.buildFunctionString + "\n} ";
    };
    WidgetStructureParser.prototype.parse = function (rootElements) {
        var scripts = [];
        var styles = [];
        var templates = [];
        for (var _i = 0, rootElements_1 = rootElements; _i < rootElements_1.length; _i++) {
            var item = rootElements_1[_i];
            if (item.type === "element") {
                var el = item;
                if (el.name === "script") {
                    scripts.push(el);
                    if (el.attrs["data-role"] == "test") {
                        var content = htmlStructure_1.structureToString(el);
                        try {
                            this.testDatas.push({
                                name: el.attrs["data-name"] || "test-" + (this.testDatas.length + 1),
                                content: htmlStructure_1.structureToString(content),
                                json: Helper.parseLooseJSON(content)
                            });
                        }
                        catch (e) {
                            this.error(content);
                            this.error("Fail to parse test data", e);
                        }
                    }
                    else if (el.attrs["data-role"] == "init") {
                        var content = htmlStructure_1.structureToString(el);
                        try {
                            this.initialData = Helper.parseLooseJSON(content);
                        }
                        catch (e) {
                            this.error(content);
                            this.error("Fail to parse init data", e);
                        }
                    }
                }
                else if (el.name === "style") {
                    styles.push(el);
                }
                else {
                    templates.push(el);
                }
            }
        }
        this.rootWidgetNode = templates[0];
        this.templateNodes = templates;
        this.styles = styles;
        this.scripts = scripts;
        if (this.rootWidgetNode) {
            var className = this.rootWidgetNode.attrs["class"] || "";
            var classes = className.split(" ").filter(function (item) { return !!item; });
            classes.unshift(CoreLogic.pathToId(this.path));
            this.rootWidgetNode.attrs["class"] = classes.join(" ");
            this.buildFunctionString = this.parseStructure(this.rootWidgetNode);
        }
    };
    WidgetStructureParser.prototype.parseStructure = function (s) {
        if (s.type === "text") {
            return this.parseText(s);
        }
        else if (s.type === "element") {
            return this.parseElement(s);
        }
    };
    WidgetStructureParser.prototype.parseText = function (text) {
        var fnName = "_t";
        return fnName + "(" + JSON.stringify(this.getDynamicStringConfig(text.content)) + ")";
    };
    WidgetStructureParser.prototype.parseElement = function (tag) {
        var _this = this;
        if (tag.name === "template") {
            this.addTemplate(tag);
            return null;
        }
        if (tag.attrs["data-list"]) {
            var listName = tag.attrs["data-list"];
            var childName = tag.attrs["data-list-item"];
            var type = void 0, reference = void 0;
            if (childName) {
                type = "reference";
                // /folder/parent ~ ./some/child => /folder/some/child
                reference = pathModule.join(this.path, "..", childName);
            }
            else {
                var templates = this.extractTemplates(tag);
                if (templates[0]) {
                    var child = templates[0];
                    if (!child.name)
                        child.name = listName + "Item";
                    type = "inline";
                    // resolve to /parent/child
                    reference = pathModule.join(this.path, "/" + child.name);
                }
                else {
                    type = "void";
                    reference = null;
                }
                (_a = this.templates).push.apply(_a, templates);
            }
            this.lists.push({
                name: listName, type: type, reference: reference
            });
        }
        if (tag.attrs["data-id"]) {
            this.addUI(tag.attrs["data-id"], tag);
        }
        if (tag.name === "slot") {
            this.slots.push({
                name: tag.attrs["data-slot"],
                tag: tag,
                index: this.slots.length
            });
            if (tag.attrs["data-slot"]) {
                this.addUI(tag.attrs["data-slot"], tag);
            }
        }
        if (tag.name === "widget") {
            var path = void 0;
            if (tag.attrs["data-widget"]) {
                path = pathModule.resolve(pathModule.dirname(this.path), tag.attrs["data-widget"]);
            }
            else {
                path = null;
            }
            var widget = {
                path: path,
                name: tag.attrs["data-name"] || CoreLogic.pathToName(tag.attrs["data-widget"] || "")
            };
            if (!widget.name) {
                this.error("widget requires a name");
            }
            this.widgets.push(widget);
        }
        var children = tag.children || [];
        var childFunctionStrings = children.map(function (tag) { return _this.parseStructure(tag); }).filter(function (item) { return !!item; });
        var fnName = "_e";
        return fnName + "(\"" + tag.name + "\", " + JSON.stringify(this.getDynamicProp(tag)) + ",[" + childFunctionStrings.join(",") + "])";
        var _a;
    };
    WidgetStructureParser.prototype.getDynamicStringConfig = function (content) {
        var result = {
            templates: Leaf.CoreLogic.createDynamicValueConfigs(content)
        };
        this.addVMByDynamicValueConfigs(result.templates);
        return result;
    };
    WidgetStructureParser.prototype.getDynamicProp = function (tag) {
        var tagName = tag.name;
        var attr = tag.attrs;
        var result = {};
        var temp = null;
        for (var name_3 in attr) {
            var value = attr[name_3];
            result[name_3] = temp = {
                name: name_3,
                templates: Leaf.CoreLogic.createDynamicValueConfigs(value)
            };
            this.addVMByDynamicValueConfigs(temp.templates);
        }
        return result;
    };
    WidgetStructureParser.prototype.addUI = function (name, tag) {
        for (var _i = 0, _a = this.UIs; _i < _a.length; _i++) {
            var ui = _a[_i];
            if (ui.name === name) {
                this.error("duplicate data-id", ui.name);
                return;
            }
        }
        this.UIs.push({
            name: name, tag: tag, tagName: tag.name
        });
    };
    WidgetStructureParser.prototype.addVM = function (name) {
        if (this.VMs.indexOf(name) >= 0)
            return;
        this.VMs.push(name);
    };
    WidgetStructureParser.prototype.addVMByDynamicValueConfigs = function (configs) {
        for (var _i = 0, configs_1 = configs; _i < configs_1.length; _i++) {
            var config = configs_1[_i];
            if (config.type === "value") {
                this.addVM(config.content);
            }
        }
    };
    WidgetStructureParser.prototype.addTemplate = function (tag, name) {
        name = tag.attrs["data-name"] || name;
        if (!name) {
            this.error("template need a name");
            return;
        }
        for (var _i = 0, _a = this.templates; _i < _a.length; _i++) {
            var template = _a[_i];
            if (template.name == name) {
                this.error("duplicate template name " + name + " ");
                return;
            }
        }
        this.templates.push({ name: name, tag: tag });
    };
    WidgetStructureParser.prototype.extractTemplates = function (e) {
        var _this = this;
        if (e.type === "text")
            return [];
        var tag = e;
        var result = [];
        if (!tag.children)
            tag.children = [];
        tag.children = tag.children.filter(function (child) {
            if (child.name === "template") {
                result.push({ name: child.attrs["data-name"], tag: child });
                return false;
            }
            result = result.concat(_this.extractTemplates(child));
            return true;
        });
        return result;
    };
    return WidgetStructureParser;
}(WidgetStructureParserDesign));
exports.WidgetStructureParser = WidgetStructureParser;
//# sourceMappingURL=context.js.map