/* tslint:disable:no-string-literal */

import ko = require("knockout");
import * as moment from "moment";
import { App } from "./app";
import { debugSettings } from "./debugsettings";
import { withCommas } from "./helpers";
import { _ } from "./languagemanager";
import * as timeService from "./timeservice";

declare var app: App;

export function registerBindings() {
    // Binding set loading variable for short amount of time.
    ko.bindingHandlers["loading"] = {
        update(
            element: HTMLElement,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            let observable: KnockoutObservable<boolean>;
            let duration: number;
            if (ko.isObservable(value)) {
                observable = value;
                duration = 500;
            } else {
                observable = value.option;
                duration = (value.duration || 500) as number;
            }

            observable(true);
            setTimeout(() => observable(false), duration);
        },
    };
    const imageBindingHandler = {
        options: {
            enabled: true,
        },
        init(
            element: HTMLElement,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            if (!imageBindingHandler.options.enabled) {
                return;
            }

            const canvas = document.createElement("canvas");
            canvas.width = window.devicePixelRatio * $(element).width();
            canvas.height = window.devicePixelRatio * $(element).height();
            canvas.style.width = "100%";
            canvas.style.height = "100%";
            element.appendChild(canvas);
            if (bindingContext["$canvas"] !== undefined) {
                // tslint:disable-next-line:no-console
                console.warn("Canvas already defined for the element." + (element.id || ""));
            }

            bindingContext["$canvas"] = canvas;
            ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                delete bindingContext["$canvas"];
            });

            const innerBindingContext = bindingContext.extend({ $canvas: canvas });
            ko.applyBindingsToDescendants(innerBindingContext, element);
            return { controlsDescendantBindings: true };
        },
        update(
            element: HTMLElement,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            if (!imageBindingHandler.options.enabled) {
                return;
            }

            const value = valueAccessor();
            const imageUrl = ko.unwrap(value) as string;
            if (imageUrl === undefined || imageUrl === null) {
                return;
            }

            const canvas = bindingContext["$canvas"] as HTMLCanvasElement;
            canvas.width = window.devicePixelRatio * $(element).width();
            canvas.height = window.devicePixelRatio * $(element).height();
            const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
            const imageLoader = new Image();
            imageLoader.onload = () => {
                ctx.drawImage(imageLoader, 0, 0, imageLoader.width, imageLoader.height);
            };
            imageLoader.src = imageUrl;
        },
    };
    ko.bindingHandlers["image"] = imageBindingHandler;
    ko.bindingHandlers["ltext"] = {
        update(
            element,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            const parameters = ko.utils.unwrapObservable(valueAccessor());
            if (parameters === null || parameters === undefined) {
                return "";
            }

            let value: string;
            if (typeof parameters === "string") {
                value = _(parameters);
            } else if (typeof parameters === "function") {
                value = _(parameters);
            } else {
                value = _(parameters.key, parameters.params);
            }

            ko.bindingHandlers.text.update(element, () => value, allBindingsAccessor, viewModel, bindingContext);
        },
    };
    ko.bindingHandlers["lattr"] = {
        update(
            element, valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const propsObject = ko.utils.unwrapObservable(valueAccessor());
            const val = {};
            for (const propName in propsObject) {
                if (propsObject.hasOwnProperty(propName)) {
                    const value = _(propsObject[propName]);
                    val[propName] = value;
                }
            }

            ko.bindingHandlers.attr.update(element, () => val, allBindingsAccessor, viewModel, bindingContext);
        },
    };
    ko.bindingHandlers["lhtml"] = {
        init(
            element, valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            ko.bindingHandlers.html.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        },
        update(
            element,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            const lockey = ko.utils.unwrapObservable(valueAccessor()) as string;
            const value = _(lockey);
            ko.bindingHandlers.html.update(element, () => value, allBindingsAccessor, viewModel, bindingContext);
        },
    };
    ko.bindingHandlers["spinner"] = {
        init(
            element,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any,
            bindingContext: KnockoutBindingContext) {
            const opts = {
                lines: 13, // The number of lines to draw
                length: 4, // The length of each line
                width: 2, // The line thickness
                radius: 6, // The radius of the inner circle
                corners: 1, // Corner roundness (0..1)
                rotate: 0, // The rotation offset
                direction: 1, // 1: clockwise, -1: counterclockwise
                color: "#fff", // #rgb or #rrggbb or array of colors
                speed: 1, // Rounds per second
                trail: 60, // Afterglow percentage
                shadow: false, // Whether to render a shadow
                hwaccel: false, // Whether to use hardware acceleration
                className: "spinner", // The CSS class to assign to the spinner
                zIndex: 2e9, // The z-index (defaults to 2000000000)
                top: "auto", // Top position relative to parent in px
                left: "auto", // Left position relative to parent in px
            };
            // tslint:disable-next-line:no-unused-variable
            const spinner = new Spinner(opts).spin(element);
        },
        update(
            element,
            valueAccessor: () => any,
            allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            ko.bindingHandlers.visible.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        },
    };
    const dateBindingHander = {
        update(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const lockey = ko.utils.unwrapObservable(valueAccessor()) as string;
            const value = _(lockey);
            ko.bindingHandlers.text.update(element, function() {
                let dateValue = moment(value);
                if (debugSettings.application.useUtcDates) {
                    dateValue = dateValue.utc();
                }

                if (timeService.timeDiff !== null) {
                    // dateValue.add(timeService.timeDiff, 'ms');
                }

                return dateValue.format(dateBindingHander.format);
            }, allBindingsAccessor, viewModel, bindingContext);
        },
        format: "D MMM, H:mm",
    };
    ko.bindingHandlers["date"] = dateBindingHander;
    ko.bindingHandlers["selector"] = {
        init(element, valueAccessor, allBindings, viewModel, bindingContext) {
            const value = valueAccessor();
            ko.bindingHandlers.click.init(element, function() {
                return function() {
                    const val = value();
                    value.options.forEach(function(item: SelectorItem) {
                        item.selected = val === item.value;
                    });
                    app.showSelector(value.caption, value.options, (item: SelectorItem) => {
                        value(item.value);
                    });
                };
            }, allBindings, viewModel, bindingContext);
        },
    };
    ko.bindingHandlers["command"] = {
        init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            let value = valueAccessor();
            value = ko.unwrap(value);
            let isFast = false;
            let commandTag = value;
            if (typeof value === "object") {
                isFast = value.fast || false;
                commandTag = value.command;
            }

            let wrapperValueAccessor: () => any;
            const handler = function(viewMovel, event) {
                if (typeof commandTag === "string") {
                    app.executeCommand(commandTag);
                } else if (typeof commandTag === "function") {
                    viewModel = bindingContext["$data"];
                    const functionValue = commandTag as (event: Event) => void;
                    functionValue.apply(viewModel, [event]);
                } else {
                    console.error("Invalid argument " + commandTag + " passed to the binding");
                }
            };
            if (platformInfo.hasTouch()) {
                wrapperValueAccessor = function(): any {
                    if (isFast) {
                        return {
                            touchstart: handler,
                        };
                    }

                    return {
                        tap: handler,
                    };
                };
            } else {
                wrapperValueAccessor = function() {
                    return {
                        click: handler,
                    };
                };
            }
            ko.bindingHandlers.event.init(
                element,
                wrapperValueAccessor,
                allBindingsAccessor,
                viewModel,
                bindingContext);
        },
    };
    const numericTextHandler = {
        update(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = ko.utils.unwrapObservable(valueAccessor()) as any;
            const positions = ko.utils.unwrapObservable(allBindingsAccessor().positions) as number || numericTextHandler.defaultPositions;
            let finalFormatted: string;
            if (value != null) {
                const numberValue = parseFloat(value);
                if (!isNaN(numberValue)) {
                    const formattedValue = numberValue.toFixed(positions);
                    finalFormatted = numericTextHandler.withCommas(formattedValue, numericTextHandler.separator);
                } else {
                    finalFormatted = "-";
                }
            } else {
                finalFormatted = "";
            }
            ko.bindingHandlers.text.update(
                element,
                function() { return finalFormatted; },
                allBindingsAccessor,
                viewModel,
                bindingContext);
        },

        defaultPositions: 2,
        separator: ",",

        withCommas,
    };
    ko.bindingHandlers["numericText"] = numericTextHandler;
    const currencySymbolBindingHandler = {
        moneySymbol: "$",
        chipsSymbol: "",
        update(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = ko.utils.unwrapObservable(valueAccessor()) as any;
            const positions = ko.utils.unwrapObservable(allBindingsAccessor()) as number;
            let text: string;
            if (value === 1) {
                text = currencySymbolBindingHandler.moneySymbol;
            } else {
                text = currencySymbolBindingHandler.chipsSymbol;
            }

            ko.bindingHandlers.text.update(
                element,
                () => text,
                allBindingsAccessor,
                viewModel,
                bindingContext);
        },
    };
    ko.bindingHandlers["currencySymbol"] = currencySymbolBindingHandler;
    interface SwipeBindingOptions {
        index: KnockoutObservable<number>;
        duration?: number;
        slideWidth?: number;
        autoFix?: boolean;
        breakDistance?: number;
        disabled?: boolean;
        updateOnSlideChange?: boolean;
    }
    interface SwipeKnockoutBindingContext extends KnockoutBindingContext {
        $swiper: Swipe;
        $swiperIndex?: number;
        insideUpdate: boolean;
        insideCallback: boolean;
        firstTime: boolean;
    }
    const swipeHandler = {
        insideUpdate: false,
        insideCallback: false,
        firstTime: true,
        logging: false,
        defaultDuration: 400,
        log(message, ...params) {
            if (swipeHandler.logging) {
                // tslint:disable-next-line:no-console
                console.log(message, params);
            }
        },
        init(
            element: HTMLElement, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: SwipeKnockoutBindingContext) {
            const valObservable: SwipeBindingOptions = valueAccessor();
            const autoFix = valObservable.autoFix === undefined ? true : !!valObservable.autoFix;
            const breakDistance = valObservable.breakDistance === undefined ? null : valObservable.breakDistance;
            if (valObservable.disabled) {
                element.style.visibility = "visible";
                return;
            }

            bindingContext.firstTime = true;
            bindingContext.insideUpdate = false;
            bindingContext.insideCallback = false;
            element.onscroll = function() {
                element.scrollLeft = 0;
            };
            const updateOnSlideChange = valObservable.updateOnSlideChange === undefined
                ? true
                : valObservable.updateOnSlideChange;
            function endHandler(index, elem) {
                    swipeHandler.log("Callback: index - ", index, ", value - ", valObservable.index());
                    if (bindingContext.insideUpdate) {
                        swipeHandler.log("swipeHandler.insideUpdate = false");
                        bindingContext.insideUpdate = false;
                        return;
                    }

                    bindingContext.insideCallback = true;
                    swipeHandler.log("swipeHandler.insideCallback = true");
                    valObservable.index(index + 1);
                    setTimeout(function() {
                        bindingContext.insideCallback = false;
                }, updateOnSlideChange ? 100 : 0);
            }

            const options: SwipeOptions = {
                startSlide: valObservable.index() - 1,
                speed: valObservable.duration || swipeHandler.defaultDuration,
                slideWidth: ko.unwrap(valObservable.slideWidth || 0),
                continuous: true,
                disableScroll: false,
                stopPropagation: false,
                breakDistance,
            };
            if (updateOnSlideChange) {
                options.callback = endHandler;
            } else {
                // When using callback instead of transitionEnd
                // The scrolling animation feels luggish when swiping very fast.
                // Luggish appears because next translation which already started and continued, was
                // stopped during updating valObservable, and thus probably causing the
                // animation restart.
                options.transitionEnd = endHandler;
            }

            bindingContext.$swiper = new Swipe(element, options);
        },
        update(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: SwipeKnockoutBindingContext) {
            const valObservable: SwipeBindingOptions = valueAccessor();
            if (valObservable.disabled) {
                return;
            }

            swipeHandler.log("Update: value - ", valObservable.index());
            bindingContext.$swiperIndex = valObservable.index();
            if (bindingContext.firstTime) {
                bindingContext.firstTime = false;
                return;
            }

            if (bindingContext.insideCallback) {
                swipeHandler.log("swipeHandler.insideCallback = false");
                swipeHandler.insideCallback = false;
                return;
            }

            const swiper = bindingContext.$swiper;
            swipeHandler.log("swipeHandler.insideUpdate = true");
            bindingContext.insideUpdate = true;
            swiper.slide(valObservable.index() - 1, valObservable.duration || swipeHandler.defaultDuration);
        },
    };
    ko.bindingHandlers["swipe"] = swipeHandler;
    ko.bindingHandlers["swipeForeach"] = {
        init(
            element: HTMLElement, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: SwipeKnockoutBindingContext) {
            swipeHandler.log("swipeForeach init ", valueAccessor()(), viewModel, bindingContext.$swiper);
            ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
            return { controlsDescendantBindings: true };
        },
        update(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: SwipeKnockoutBindingContext) {
            swipeHandler.log("swipeForeach update ", valueAccessor()(), viewModel, bindingContext.$swiper);
            ko.bindingHandlers.foreach.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
            swipeHandler.log("Preious slider position ", bindingContext.$swiperIndex);
            if (bindingContext.$swiper) {
                bindingContext.$swiper.kill();
                bindingContext.$swiper.setup();
                bindingContext.$swiper.attachEvents();
                bindingContext.$swiper.slide(bindingContext.$swiperIndex - 1, 0);
            }
        },
    };
    ko.virtualElements.allowedBindings["swipeForeach"] = true;
    interface ScrollBindingOptions {
        refreshTrigger: KnockoutObservable<any>;
        suppress?: boolean;
    }
    interface ScrollKnockoutBindingContext extends KnockoutBindingContext {
        $scroll: IScroll;
    }
    const scrollHandler = {
        logging: false,
        defaultDuration: 400,
        log(message, ...params) {
            if (scrollHandler.logging) {
                // tslint:disable-next-line:no-console
                console.log(message, params);
            }
        },
        setup(element: HTMLElement, options: ScrollBindingOptions) {
            let scroller: IScroll;
            const scrollerOptions = {
                hScrollbar: false,
                vScrollbar: false,
                lockDirection: true,
                deceleration: 0.0001,
                bounceEasing: "quadratic",
            };
            setTimeout(() => {
                scrollHandler.log("Initializing scroller for the first time");
                scroller = new IScroll(element, scrollerOptions);
                if ($(element).css("box-sizing") === "border-box") {
                    const top = parseInt($(".registration .popup-container").css("padding-top"), 10) || 0;
                    const bottom = parseInt($(".registration .popup-container").css("padding-bottom"), 10) || 0;
                    const adjustment = top + bottom;
                    // tslint:disable-next-line:no-console
                    console.log(scroller.maxScrollY, top, bottom);
                    if (scroller.maxScrollY > 0) {
                        scroller.maxScrollY = scroller.maxScrollY + adjustment;
                    } else {
                        scroller.maxScrollY = scroller.maxScrollY - adjustment;
                    }
                }

                ko.utils.domData.set(element, "iscroll", scroller);
            }, 500);
            /* We have non zero timeout here above to prevent scroll area in the lobby
            from freezing for the first time when it is opened.*/
            ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                // This will be called when the element is removed by Knockout or
                // if some other part of your code calls ko.removeNode(element)
                scrollHandler.log("Disposing scroller");
                const iscroller = ko.utils.domData.get(element, "iscroll") as IScroll;
                if (iscroller != null) {
                    iscroller.destroy();
                }
            });
            if (options.refreshTrigger != null) {
                const left = ko.computed(function() {
                    return options.refreshTrigger();
                }).extend({ rateLimit: 10 }).subscribe(function(value) {
                    setTimeout(() => {
                        scrollHandler.log("Refresh trigger");
                        const iscroller = ko.utils.domData.get(element, "iscroll") as IScroll;
                        if (iscroller != null) {
                            iscroller.refresh();
                        }
                    }, 0);
                });
            }
        },
        init(
            element: HTMLElement, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: ScrollKnockoutBindingContext) {
            const valObservable: ScrollBindingOptions = valueAccessor();
            const suppressed = valObservable.suppress || false;
            if (!suppressed) {
                scrollHandler.setup(element, valObservable);
            }
        },
    };
    ko.bindingHandlers["scroll"] = scrollHandler;
    ko.bindingHandlers["bet"] = {
        update(
            element: HTMLElement, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = ko.unwrap(valueAccessor());
            if (value === null || value === undefined || value === 0) {
                element.innerHTML = "";
            } else {
                while (element.firstChild) {
                    element.removeChild(element.firstChild);
                }

                const chipItem = new ChipItem(1);
                const stackInfo = chipItem.getData(value);
                const container = document.createElement("div");
                if (stackInfo.length === 1) {
                    container.setAttribute("class", "chip-container stack1");
                } else {
                    container.setAttribute("class", "chip-container stack2");
                }

                for (let i = 0; i < stackInfo.length; i++) {
                    const chipsInfo = stackInfo[i];
                    const stack = document.createElement("div");
                    stack.setAttribute("class", "chip chip-stack");
                    for (let j = 0; j < chipsInfo.length; j++) {
                        const chip = document.createElement("div");
                        chip.setAttribute("class", "chip chip" + chipsInfo[j].toString());
                        stack.appendChild(chip);
                    }

                    container.appendChild(stack);
                }

                element.appendChild(container);
                const label = document.createElement("div");
                label.setAttribute("class", "label");
                label.innerText = withCommas(value.toFixed(0), ",");
                element.appendChild(label);
            }
        },
    };
    ko.bindingHandlers["forceFocus"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const handler = function(data, event: Event) {
                setTimeout(() => {
                    const currentTargetElement = event.currentTarget as HTMLElement;
                    currentTargetElement.focus();
                }, 0);
            };
            ko.bindingHandlers.event.init(element, function() { return { touchstart: handler }; },
                allBindingsAccessor, viewModel, bindingContext);
        },
    };
    ko.bindingHandlers["slideup"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { dragMinDistance: 3 });
            hammer.on("dragup", function(event) {
                functionValue.apply(viewModel);
            });
        },
    };

    ko.bindingHandlers["slidedown"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { dragMinDistance: 3 });
            hammer.on("dragdown", function(event) {
                functionValue.apply(viewModel);
            });
        },
    };
    ko.bindingHandlers["slideleft"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { threshold: 10 });
            hammer.on("swipeleft", function(event) {
                functionValue.apply(viewModel);
            });
        },
    };

    ko.bindingHandlers["slideright"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { threshold: 10 });
            hammer.on("swiperight", function(event) {
                functionValue.apply(viewModel);
            });
        },
    };

    ko.bindingHandlers["doubletap"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { tap: true });
            hammer.on("doubletap", function(event) {
                if (event.gesture.target === element) {
                    functionValue.apply(viewModel);
                }
            });
        },
    };

    ko.bindingHandlers["handle"] = {
        init(
            element: HTMLElement, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: KnockoutBindingContext) {
            const value = valueAccessor();
            const functionValue = value as () => void;
            const hammer = Hammer(element, { drag_block_horizontal: true, drag_lock_to_axis: true });
            const parent = element.parentElement;
            hammer.on("drag", function(event) {
                const touch = event.gesture.touches[0];
                let offset = 50;
                if (PageBlock.useDoubleView) {
                    if ($(element).width() === 800) {
                        offset = $(element).width() / 8;
                    } else {
                        offset = $(element).width() / 4;
                    }
                }

                const relativePosition = touch.pageX - $(parent).offset().left - offset;
                const currentRelativePosition = functionValue.apply(viewModel);
                if (Math.abs(currentRelativePosition - relativePosition) < $(".slider-handle").width()) {
                    viewModel.setPosition(relativePosition);
                }
            });
        },
    };
    interface FadeTableBindingSettings {
        item: KnockoutSubscribable<any>;
        target: KnockoutObservable<boolean>;
        duration?: number;
    }
    interface FadeTableBindingContext extends KnockoutBindingContext {
        $timeout: number;
    }
    ko.bindingHandlers["fadeTable"] = {
        init(
            element, valueAccessor: () => any, allBindingsAccessor: KnockoutAllBindingsAccessor,
            viewModel: any, bindingContext: FadeTableBindingContext) {
            const value = valueAccessor() as FadeTableBindingSettings;
            const duration = value.duration || 1000;
            const subscription = value.item.subscribe(function(val) {
                if (bindingContext.$timeout != null) {
                    return;
                }

                const continuation = () => {
                    value.target(false);
                    bindingContext.$timeout = null;
                };
                value.target(true);
                bindingContext.$timeout = setTimeout(continuation, duration);
            });
            ko.utils.domNodeDisposal.addDisposeCallback(element, () => subscription.dispose());
        },
    };
}
