/// <reference path="leaf.ts"/>
namespace Leaf {
    // Any element belongs to this element may implement this interface
    export interface WidgetElement extends HTMLElement {
        // If the element if the root node of any Widget
        // In some cases, root node is shared, for example using a commonWidget as top node of a widget.
        // <modal><div data-slot='content'></div></modal>
        // When it happens, widget always point to the child widget, which is <modal> in the example.
        // At time of writing, widget reference is only used for retreiving custom DOM event provided by the widget.
        widget: WidgetAny
        // All element have it's property rules
        propertyConfigs: WidgetDynamicPropertyConfig[]
    }
    // A rule for generating the element's property
    export interface WidgetDynamicPropertyConfig {
        master: WidgetAny
        rules: DynamicProperties
    }
    export abstract class WidgetCoreDesign {
        constructor(protected widget: WidgetAny) { }
        public subWidgetPlaceholder: {
            [index: string]: {
                holder: HTMLElement
                dynamics: DynamicProperties
                slots: Slot[]
            }
        } = {}
        public slotsPlaceholder: {
            [index: string]: {
                el: HTMLElement
                name: string
                dynamics: DynamicProperties
                index: number
            }
        } = {}
        public slotIndexOffset = 0
        abstract attachElement(el: WidgetElement, dynamics: DynamicProperties);
        abstract detachElement(el: WidgetElement);
        abstract attachText(text: Text, config: DynamicStringConfig);
        abstract detachText(text: Text);
        // trim attribute with dynamic property string, and return the parsed dynamic properties.
        abstract extractDynamicPropertyConfig(el: WidgetElement): DynamicProperties;
        abstract extractDynamicStringConfig(content: string): DynamicStringConfig
        // 1. attach listeners
        // 2. add dynamic rule to property configs of this element(and filter out the old one)
        // 3. evaluate once for now.
        abstract attachDynamicProperty(el: WidgetElement, dynamics: DynamicProperties);
        // 1. remove listeners
        // 2. remove dynamic rules
        // 3. revaluate for now.
        abstract detachDynamicProperty(el: WidgetElement);
        abstract getExistDynamicProperty(el: WidgetElement): DynamicProperties;
        // evaluate all dynamic properties and apply 
        abstract syncDynamicProperties(el: WidgetElement);
        // evaluate only changed property
        abstract syncDynamicProperty(el: WidgetElement, name: string);
        abstract syncDynamicText(text: Text, rule: DynamicStringConfig);
        abstract attachDynamicText(text: Text, rule: DynamicStringConfig);
        abstract detachDynamicText(text: Text);
        abstract bindUI(node: WidgetElement, id: string);
        abstract unbindUI(id: string);
        abstract checkAndBindUI(el: WidgetElement);
        abstract checkAndUnbindUI(el: WidgetElement);
        protected abstract explain(config: DynamicValueConfig);
        // slot related
        abstract defineSlot(name: string, el: Element, dynamics: DynamicProperties);
        abstract fillSlots(slots: Slot[]);
        // widget
        abstract defineSubWidget(name: string, el: WidgetElement, dynamics: DynamicProperties, slots: Slot[]);
        abstract setSubWidget(name: string, widget);
        abstract mergeProps(from: HTMLElement, to: HTMLElement, props: string[])
        // list
        abstract defineList(name: string, el: HTMLElement)
        // helper
        abstract extractTemplates(node: HTMLElement): { [index: string]: string }
        abstract transferProperties(to: WidgetElement, dynamics: DynamicProperties)
    }
    export abstract class WidgetInitializerDesign {
        constructor(protected widget: WidgetAny) { }
        abstract initTemplate(template?: WidgetTemplate);
        abstract traverseAndInitialize(node: WidgetElement | HTMLElement | Text);
        abstract createElement(tagName: string, properties: ElementConfigProperties, children: WidgetElement[]): WidgetElement;
        abstract createText(rule: DynamicStringConfig);
    }
    export class WidgetCore extends WidgetCoreDesign {
        attachElement(el: WidgetElement, dynamics: DynamicProperties): WidgetElement {
            // 1. widget
            // 2. common widget
            // 3. slot
            // 4. normal
            // generally todos
            // 0. create real element if needed
            // 1. bind and sync props
            // 2. bindUI by data id
            let tagName = el.tagName.toLowerCase()
            if (tagName.toLowerCase() === "widget") {
                // widget
                let name = el.getAttribute("data-name")
                let path = el.getAttribute("data-widget")
                if (!name && path) {
                    let last = path.split("/").pop()
                    last = last.split(".")[0]
                    name = last
                }
                if (!name) {
                    console.error(el, "widget missing name")
                    throw new Error("<widget> missing data-name or data-widget")
                }
                this.defineSubWidget(name, el, dynamics, this.getChildrenAsSlot(el))
            } else if (this.widget.namespace.has(tagName) && !this.widget.namespace.isTagWidgetName(tagName, this.widget)) {
                // common widget
                let props = {}
                // none dynamic props
                for (let i = 0; i < el.attributes.length; i++) {
                    let item = el.attributes[i]
                    props[item.name] = item.value
                }
                // slots
                let customWidget = this.widget.namespace.createWidgetByName(tagName, props)
                // inherit properties
                for (let name in props) {
                    if (!customWidget.node.getAttribute(name)) {
                        // resolve data-id-conflict 
                        if (name === "data-id") name = "data-id-of-parent"
                        customWidget.node.setAttribute(name, props[name])
                    }
                }
                customWidget.core.fillSlots(this.getChildrenAsSlot(el))
                // use the data-id of placeholder to bindUI
                this.checkAndBindUI(el)
                // apply properties latter on this custom widget than this place holder
                el = customWidget.node as WidgetElement
                // sync props after end of this if statement...
            } else if (tagName === "slot") {
                // slot
                let name = el.getAttribute("data-slot")
                this.defineSlot(name, el, dynamics)
                // We also bind UI temperarily until filled
                this.checkAndBindUI(el)
            } else {
                // normal
                // Id-for-parent priorier to data-id.
                this.checkAndBindUI(el)
                if (el.getAttribute("data-list")) {
                    let name = el.getAttribute("data-list")
                    this.defineList(name, el)
                }
            }
            this.attachDynamicProperty(el, dynamics)
            this.syncDynamicProperties(el)
            // Whatever it is, we initialize and sync the properties on it
            return el
        }
        detachElement(el: WidgetElement) {
            if (!el) return
            this.detachDynamicProperty(el)
        };
        attachText(text: Text, config: DynamicStringConfig) {
            for (let item of config.templates) {
                if (item.type !== "value") continue
                this.widget.ViewModel.events.listenBy(text, [`change/${item.content}`, `depChange/${item.content}`], this.syncDynamicText.bind(this, text, config))
            }
            this.syncDynamicText(text, config)
        }
        detachText(text: Text) {
            this.widget.ViewModel.events.stopListenBy(text)
        }
        // Should ignore `data-` properties
        extractDynamicPropertyConfig(el: WidgetElement): DynamicProperties {
            let properties: DynamicProperties = {}
            for (let i = 0; i < el.attributes.length; i++) {
                let attr = el.attributes[i]
                let configs = Leaf.CoreLogic.createDynamicValueConfigs(attr.value)
                if (configs && configs.length > 0) {
                    el.removeAttribute(attr.name)
                    properties[attr.name] = {
                        name: attr.name,
                        templates: configs
                    }
                }
            }
            return properties

        }
        getExistDynamicProperty(el: WidgetElement): DynamicProperties {
            for (let props of el.propertyConfigs || []) {
                if (props.master === this.widget) return props.rules
            }
            return null
        }
        syncDynamicProperties(el: WidgetElement) {
            let props: { [index: string]: string[] } = {}
            for (let config of el.propertyConfigs || []) {
                let vm = config.master.ViewModel
                for (let name in config.rules) {
                    let rule = config.rules[name]
                    let value = Leaf.CoreLogic.explainDynamicString(vm, rule)
                    if (!props[name]) props[name] = []
                    props[name].push(value)
                }
            }
            let result: { [index: string]: string } = {}
            for (let name in props) {
                if (props[name].length > 1 && Leaf.CoreLogic.isPropertyMergable(name)) {
                    result[name] = Leaf.CoreLogic.mergeProperty(name, props[name])
                } else {
                    result[name] = props[name].pop()
                }
            }
            for (let name in result) {
                let value = result[name]
                if (value !== el.getAttribute(name)) {
                    el.setAttribute(name, value)
                }
            }
        }
        syncDynamicProperty(el: WidgetElement, propertyName: string) {
            let values: string[] = []
            for (let config of el.propertyConfigs) {
                for (let name in config.rules) {
                    if (name === propertyName) {
                        values.push(Leaf.CoreLogic.explainDynamicString(config.master.ViewModel, config.rules[name]))
                    }
                }
            }
            let value = Leaf.CoreLogic.mergeProperty(propertyName, values)
            if (value.toString() != el.getAttribute(propertyName).toString()) {
                el.setAttribute(propertyName, value)
            }
        }
        syncDynamicText(text: Text, rule: DynamicStringConfig) {
            // Should add pollyfill for IE8/7
            // https://developer.mozilla.org/en/docs/Web/API/Node/textContent
            let value = text.textContent
            let strs = rule.templates.map((t) => {
                return this.explain(t)
            })
            let result = strs.join("")
            if (result !== value) {
                text.textContent = result
            }
        }
        explain(config: DynamicValueConfig) {
            return Leaf.CoreLogic.explainDynamicValue(this.widget.ViewModel, config)
        }
        checkAndUnbindUI(el: WidgetElement) {
            let id = Leaf.Util.slugToCamel(el.getAttribute("data-id"))
            if (id && this.widget.UI[id] === el) {
                this.unbindUI(id)
            }
        }
        checkAndBindUI(el: WidgetElement) {
            let id = Leaf.Util.slugToCamel(el.getAttribute("data-id"))
            if (id) {
                this.bindUI(el, id)
            }
        }
        bindUI(node: WidgetElement, id: string) {
            if (this.widget.UI[id] === node) return
            this.unbindUI(id);
            this.widget.UI[id] = node
            this.widget.delegates.applyUIDelegateOwner(id)
        }
        unbindUI(id: string) {
            this.widget.delegates.clearUIDelegateOwner(id)
            delete this.widget.UI[id]
        }
        defineSubWidget(name: string, el: WidgetElement, dynamics: DynamicProperties, slots: Slot[]) {
            // I've considered using widget.subWidget[name], but after
            // consider twice, I think directly assign subWidget to widget
            // is a better practice and name collision should be minimum.

            // First set this element to it
            this.subWidgetPlaceholder[name] = {
                holder: el
                , dynamics: dynamics
                , slots
            }
            Leaf.Property.define(this.widget, name, {
                set: (value: WidgetAny, cache: PropertyCache<WidgetAny>) => {
                    if (value instanceof Leaf.Widget || !value) {
                        // First assignment do nothing, it's likely the placeholder <widget>
                        this.setSubWidget(name, value)
                        cache.value = value
                    }
                }
            })
        }
        setSubWidget(name: string, widget: WidgetAny) {
            // 0. detach old
            // 1. sync properties
            // 2. bindUI
            // 3. replace element

            // replace
            let oldWidget = this.widget[name] as WidgetAny
            let replaceTarget: HTMLElement
            let replaceSrc: HTMLElement
            if (oldWidget && oldWidget.node && oldWidget.node.parentElement) {
                replaceTarget = oldWidget.node
            } else {
                replaceTarget = this.subWidgetPlaceholder[name].holder
            }
            if (widget && widget.node) {
                replaceSrc = widget.node
            } else {
                replaceSrc = this.subWidgetPlaceholder[name].holder
            }
            if (replaceSrc !== replaceTarget) {
                replaceTarget.parentElement.replaceChild(replaceSrc, replaceTarget)
            }
            // detach old
            if (oldWidget) {
                this.detachElement(oldWidget.node as WidgetElement)
                this.checkAndUnbindUI(oldWidget.node)
            }
            if (widget) {
                // transfer static property to it
                // transfer dynamic property to it and bind UI
                let holder = this.subWidgetPlaceholder[name]
                this.transferProperties(widget.node, holder.dynamics)
                this.attachElement(widget.node, null)
                widget.core.fillSlots(this.subWidgetPlaceholder[name].slots)
            }
        }
        attachDynamicProperty(el: WidgetElement, dynamics?: DynamicProperties) {
            if (!el.propertyConfigs) el.propertyConfigs = []
            //console.log("attach", dynamics, el.propertyConfigs)
            let todo: DynamicProperties[] = []
            this.widget.ViewModel.events.stopListenBy(el)
            if (dynamics) {
                el.propertyConfigs.push({ master: this.widget, rules: dynamics });
            }
            for (let config of el.propertyConfigs) {
                // already attached
                if (config.master === this.widget) {
                    todo.push(config.rules)
                }
            }
            if (todo.length > 1) {
                console.warn("Multiple dynamic attaches")
                console.trace()
            }
            for (let dynamics of todo) {
                for (let name in dynamics) {
                    let config = dynamics[name]
                    for (let part of config.templates) {
                        if (part.type === "raw") continue
                        this.widget.ViewModel.events.listenBy(
                            el,
                            [`change/${part.content}`, `depChange/${part.content}`],
                            this.syncDynamicProperty.bind(this, el, name)
                        )
                    }
                }
            }
        }
        detachDynamicProperty(el: WidgetElement) {
            // Detach and remove properties
            if (!el) return
            if (!el.propertyConfigs) el.propertyConfigs = []
            el.propertyConfigs = el.propertyConfigs.filter(item => item.master !== this.widget)
            this.widget.ViewModel.events.stopListenBy(el)
        }
        attachDynamicText(text: Text, rule: DynamicStringConfig) {
            for (let part of rule.templates) {
                if (part.type === "raw") continue
                this.widget.ViewModel.events.listenBy(
                    text,
                    [`change/${part.content}`, `depChange/${part.content}`],
                    this.syncDynamicText.bind(this, text, rule)
                )
            }
        }
        detachDynamicText(text: Text) {
            this.widget.ViewModel.events.stopListenBy(text)
        }
        extractTemplates(node: HTMLElement) {
            let result = {}
            let templates = node.querySelectorAll("template")
            for (let i = 0; i < templates.length; i++) {
                let item = templates[i]
                if (node.contains(item)) {
                    item.parentElement.removeChild(item)
                    let name = item.getAttribute("data-name")
                    if (name) {
                        result[name] = item.innerHTML
                    }
                }
            }
            return result
        }
        defineSlot(name: string, el: Element, dynamics: DynamicProperties) {
            // refactor slot implementation and abstract it to the same
            // way as subwidget.
            this.slotsPlaceholder[name] = {
                el: el as HTMLElement,
                name: name,
                index: this.slotIndexOffset++,
                dynamics: dynamics
            }
        }
        fillSlots(slots: Slot[]) {
            for (let i = 0; i < slots.length; i++) {
                let slot = slots[i]
                let holder = this.getSlotHolder(slot.name, i)
                if (holder) {
                    holder.el.parentElement.replaceChild(slot.el, holder.el)
                    holder.el = slot.el as HTMLElement
                    this.transferProperties(slot.el as WidgetElement, holder.dynamics)
                    this.bindUI(slot.el as WidgetElement, holder.name)
                }
            }
        }
        extractDynamicStringConfig(content: string): DynamicStringConfig {
            let config = Leaf.CoreLogic.createDynamicValueConfigs(content)
            return {
                templates: config
            }
        }
        mergeProps(from: HTMLElement, to: HTMLElement, props: string[]) {
            for (let prop of props) {
                let toPropValue = to.getAttribute(prop)
                let fromPropValue = from.getAttribute(prop)
                if (fromPropValue !== toPropValue) {
                    if (Leaf.CoreLogic.isPropertyMergable(prop)) {
                        to.setAttribute(prop, Leaf.CoreLogic.mergeProperty(prop, [fromPropValue, toPropValue]))
                    } else {
                        to.setAttribute(prop, fromPropValue)
                    }
                }
            }
        }
        transferProperties(to: WidgetElement, dynamics: DynamicProperties) {
            if (!to.propertyConfigs) to.propertyConfigs = []
            to.propertyConfigs = to.propertyConfigs.filter(item => item.master !== this.widget)
            dynamics = Leaf.Util.clone(dynamics)
            this.attachDynamicProperty(to, dynamics)
            this.syncDynamicProperties(to)
        }
        defineList(name: string, el: HTMLElement) {
            let list = this.widget[name] = Leaf.Widget.makeList(el)
            let events = ["child/add", "child/remove"]
            for (let event of events) {
                let camelEventName = event.replace(/\/[a-z]/ig, (match) => {
                    return match[1].toUpperCase()
                })
                let handlerName = `on` + Leaf.Util.capitalize(camelEventName) + Leaf.Util.capitalize(name)
                Leaf.Property.define(this.widget, handlerName, {
                    set: (value: Function, cache) => {
                        if (cache.value) {
                            this.widget[name].events.removeEventListener(event, cache.value)
                        }
                        if (value) {
                            cache.value = (...args) => {
                                value.apply(this.widget, args)
                            }
                            this.widget[name].events.on(event, cache.value)
                        } else {
                            cache.value = null
                        }
                    }
                })
                if (this.widget[handlerName]) {
                    this.widget[handlerName] = this.widget[handlerName]
                }
            }
            list.events.listenBy(this, "child/remove", (child) => {
                child.events.stopListenBy(this)
            })
        }
        protected getPropsFromElement(el: HTMLElement) {
            let result = {}
            let attrs = el.attributes
            for (let i = 0; i < attrs.length; i++) {
                let item = attrs[i]
                result[item.name] = item.value
            }
            return result
        }
        protected getChildrenAsSlot(el: HTMLElement): Slot[] {
            let slots: Slot[] = []
            if (el.childNodes.length === 1 && el.childNodes[0] instanceof Text) {
                let result = document.createElement("span")
                result.appendChild(el.childNodes[0])
                return [{
                    el: result,
                    name: "slot0"
                }]
            }
            for (let i = 0; i < el.children.length; i++) {
                let item = el.children[i]
                let name = item.getAttribute("data-slot") || `slot${i}`
                slots.push({ name, el: item })
            }
            return slots
        }
        protected getSlotHolder(name: string, index: number) {
            for (let s in this.slotsPlaceholder) {
                let item = this.slotsPlaceholder[s]
                if (item.name === name) return item
                if (item.index === index) return item
            }
            return null
        }
    }
    export class WidgetInitializer extends WidgetInitializerDesign {
        initTemplate(template?: WidgetTemplate) {
            if (!template) {
                template = document.createElement("div")
            }
            // All kind of template
            if (typeof template === "string") {
                let tempNode = document.createElement("div");
                tempNode.innerHTML = template.trim()
                this.widget.node = tempNode.children[0] as WidgetElement
                tempNode.removeChild(this.widget.node)
            } else if (template instanceof Element) {
                this.widget.node = template as WidgetElement
            } else if (typeof template === "function") {
                return this.initTemplate(template())
            }
            if (!this.widget.node) {
                console.error("Invalid Widget.node constructed with template", template)
                throw new Error("BrokenWidgetTemplate")
            }
            //if (this.widget.node.getAttribute("data-id")) {
            //    console.error("Shouldn't bind data-id on root of the any widget you can always refer to them as `node`. `data-id` may latter be assigned by parent widget.");
            //    throw new Error("Invalid data id assignment")
            //}
            this.widget.node.widget = this.widget
            if (this.widget.node.nodeType === this.widget.node.TEXT_NODE) return
            this.widget.templates = this.widget.core.extractTemplates(this.widget.node)
            this.traverseAndInitialize(this.widget.node)
            this.widget.core.bindUI(this.widget.node, "node")
        }
        traverseAndInitialize(node: WidgetElement | Text) {
            if (node instanceof HTMLElement) {
                for (let i = 0; i < node.childNodes.length; i++) {
                    let item = node.childNodes[i]
                    this.traverseAndInitialize(item as WidgetElement | Text)
                }
                let config = this.widget.core.extractDynamicPropertyConfig(node)
                this.widget.core.attachElement(node, config)
            } else if (node instanceof Text) {
                let config = this.widget.core.extractDynamicStringConfig(node.textContent)
                this.widget.core.attachText(node, config)
            }
        }
        createElement(tagName: string, properties: ElementConfigProperties, children: WidgetElement[]): WidgetElement {
            let el = document.createElement(tagName)
            let frag = document.createDocumentFragment()
            for (let child of children) {
                frag.appendChild(child)
            }
            let dynamics: DynamicProperties = {}
            for (let name in properties) {
                let v = properties[name]
                if (typeof v === "string") {
                    el.setAttribute(name, v)
                    el[name] = v
                } else {
                    dynamics[name] = v
                    el.setAttribute(name, Leaf.CoreLogic.explainDynamicString(this.widget.VM, v))
                }
            }
            el.appendChild(frag)
            el = this.widget.core.attachElement(el as WidgetElement, dynamics)
            return el as WidgetElement
        }
        createText(rule: DynamicStringConfig): Text {
            let text = document.createTextNode("")
            this.widget.core.attachText(text, rule)
            return text
        }
    }
    export class WidgetDOMEventDelegator {
        constructor(public readonly widget) {
        }
        private lazyHandlers: {
            [index: string]: {
                [eventName: string]: {
                    handler?: WidgetEventHandler
                }
            }
        } = {}
        private prepareUIDelegates(uiName: string) {
            if (this.lazyHandlers[uiName]) return
            this.lazyHandlers[uiName] = {}
            let events = Leaf.CoreLogic.AllEvents.slice(0)
            let ui = this.widget.UI[uiName] as WidgetElement
            if (ui.widget && ui.widget.providingEvents && ui.widget.providingEvents.length > 0) {
                events = events.concat(ui.widget.providingEvents)
            }
            for (let event of events) {
                this.prepareUIDelegate(uiName, event.name)
            }
        }
        private prepareUIDelegate(uiName: string, eventName: string) {
            if (this.lazyHandlers[uiName][eventName]) return
            this.lazyHandlers[uiName][eventName] = {}
            let handlerName = `on${Leaf.Util.capitalize(eventName)}${Leaf.Util.capitalize(uiName)}`
            Leaf.Property.define<WidgetEventHandler>(this, handlerName, {
                set: (v, cache) => {
                    if (typeof v != "function") return
                    cache.value = v
                    this.unbindLazyHandler(uiName, eventName)
                    this.provideLazyHandler(uiName, eventName, v)
                    this.bindLazyHandler(uiName, eventName)
                }
            })
            // only set handler, dont' unbind or bind it.
            // apply/clear delegate will do it.
            if (this.widget[handlerName]) {
                this.provideLazyHandler(uiName, eventName, this.widget[handlerName])
            }
            // for onClick => onClickNode
            if (uiName === "node") {
                let handlerName = `on${Leaf.Util.capitalize(eventName)}`
                Leaf.Property.define<WidgetEventHandler>(this, handlerName, {
                    set: (v, cache) => {
                        if (typeof v != "function") return
                        cache.value = v
                        this.unbindLazyHandler(uiName, eventName)
                        this.provideLazyHandler(uiName, eventName, v)
                        this.bindLazyHandler(uiName, eventName)
                    }
                })
                if (this.widget[handlerName]) {
                    this.provideLazyHandler(uiName, eventName, this.widget[handlerName])
                }
            }
        }
        // call unbind/bind/provide carefully, or just don't call them.
        private unbindLazyHandler(uiName: string, eventName: string) {
            let owner = this.widget.UI[uiName] as HTMLElement
            let info = this.lazyHandlers[uiName][eventName] || {}
            if (info.handler) {
                owner.removeEventListener(eventName, info.handler)
            }
        }
        private bindLazyHandler(uiName: string, eventName: string) {
            let owner = this.widget.UI[uiName] as HTMLElement
            let info = this.lazyHandlers[uiName][eventName] || {}
            if (info.handler) {
                owner.addEventListener(eventName, info.handler)
            }
        }
        private provideLazyHandler(uiName: string, eventName: string, v: Function) {
            let handler = (e: WidgetEnhancedEvent) => {
                e.capture = () => {
                    e.stopImmediatePropagation()
                    e.preventDefault()
                }
                v.call(this.widget, e)
            }
            this.lazyHandlers[uiName][eventName].handler = handler
        }
        public clearUIDelegateOwner(id: string) {
            if (!this.widget.UI[id]) return
            let owner = this.widget.UI[id] as HTMLElement
            let infos = this.lazyHandlers[id] || {}
            for (let eventName in infos) {
                let info = infos[eventName]
                if (info.handler) {
                    owner.removeEventListener(eventName, info.handler)
                }
            }
        }
        // Carefull call apply UI delegate owner
        // 1. call clear
        // 2. set UI[uiName]
        // 3. call apply
        // Always call them like above.
        // But feel free to call `clear` at any time.
        public applyUIDelegateOwner(id: string) {
            if (!this.widget.UI[id]) return
            let owner = this.widget.UI[id] as HTMLElement
            this.prepareUIDelegates(id)
            let infos = this.lazyHandlers[id]
            // bind any existing events
            for (let eventName in infos) {
                let info = infos[eventName]
                if (info.handler) {
                    owner.addEventListener(eventName, info.handler)
                }
            }
        }
        private delegateTo(name: string, event: Event) {
            let fnName = `on${Util.capitalize(event.type)}${Util.capitalize(name)}`
            let fn = this.widget[fnName] as WidgetEventHandler
            if (!fn) {
                return true
            }
            return fn.call(this.widget, event)
        }
    }
}
