"use strict";
/**
 * Copyright (c) Haiku 2016-2018. All rights reserved.
 */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var React = require("react");
var ReactDOM = require("react-dom");
var EventsDict_1 = require("./EventsDict");
var Config_1 = require("../../Config");
var StringUtils_1 = require("../../helpers/StringUtils");
var getParsedProperty_1 = require("../../helpers/getParsedProperty");
var DEFAULT_HOST_ELEMENT_TAG_NAME = 'div';
// tslint:disable-next-line:function-name
function HaikuReactDOMAdapter(haikuComponentFactory, optionalRawBytecode) {
    var HaikuReactComponentInternal = /** @class */ (function (_super) {
        __extends(HaikuReactComponentInternal, _super);
        function HaikuReactComponentInternal(props) {
            var _this = _super.call(this, props) || this;
            _this.state = {
                // This random id is used to give us a hook to query the DOM for our mount element,
                // even in cases where React mysteriously decides not to pass us its ref.
                randomId: 'haiku-reactroot-' + StringUtils_1.randomString(24),
            };
            return _this;
        }
        HaikuReactComponentInternal.prototype.componentWillReceiveProps = function (nextPropsRaw) {
            if (this.haiku) {
                var haikuConfig = this.buildHaikuCompatibleConfigFromRawProps(nextPropsRaw);
                this.haiku.assignConfig(haikuConfig);
            }
        };
        HaikuReactComponentInternal.prototype.componentWillUnmount = function () {
            if (this.haiku) {
                this.haiku.callUnmount();
            }
        };
        HaikuReactComponentInternal.prototype.componentDidMount = function () {
            this.attemptMount();
        };
        HaikuReactComponentInternal.prototype.attemptMount = function () {
            if (this.mount) {
                this.createContext(this.props);
            }
        };
        HaikuReactComponentInternal.prototype.buildHaikuCompatibleConfigFromRawProps = function (rawProps) {
            // Note that these vanities are called _after_ an initial render,
            // i.e., after this.mount is supposed to have been attached.
            var haikuConfig = {
                ref: this.mount,
                vanities: {
                    'controlFlow.placeholder': function _controlFlowPlaceholderReactVanity(element, surrogate, value, context, timeline, receiver, sender) {
                        visit(this.mount, function (node) {
                            var flexId = flexIdIfSame(element, node);
                            if (flexId) {
                                if (element.__surrogate !== surrogate) {
                                    if (typeof surrogate.type === 'string' ||
                                        (typeof surrogate.type === 'function' && surrogate.type.isHaikuAdapter)) {
                                        // What *should happen* in the DOM renderer is:
                                        // this new swapped DOM element will be updated (not replaced!)
                                        // with the attributes of the virtual element at the same position
                                        var div = document.createElement('div');
                                        node.parentNode.replaceChild(div, node);
                                        // tslint:disable-next-line:no-parameter-reassignment
                                        node = div;
                                        // We have to change the element name as well here so that the correct vanity behaviors
                                        // are used when applying outputs to the placeheld element (e.g. opacity vs style.opacity).
                                        element.elementName = 'div';
                                    }
                                    node.style.visibility = 'hidden';
                                    ReactDOM.render(surrogate, node);
                                    window.requestAnimationFrame(function () {
                                        element.__surrogate = surrogate;
                                        node.style.visibility = 'visible';
                                    });
                                    sender.markHorizonElement(element);
                                    sender.markForFullFlush();
                                }
                            }
                        });
                    }.bind(this),
                },
            };
            // It's assumed that anything the user wants to pass into the Haiku engine should be
            // assigned among the whitelisted properties
            if (rawProps) {
                for (var verboseKeyName in rawProps) {
                    // We already should have subscribed at the React host element level;
                    // we don't pass in otherwise we can end up with multiple events fired
                    if (EventsDict_1.default[verboseKeyName]) {
                        continue;
                    }
                    haikuConfig = __assign({}, haikuConfig, getParsedProperty_1.default(rawProps, verboseKeyName));
                }
            }
            return haikuConfig;
        };
        HaikuReactComponentInternal.prototype.createContext = function (rawProps) {
            var haikuConfig = this.buildHaikuCompatibleConfigFromRawProps(rawProps);
            var haikuAdapter;
            if (rawProps.haikuAdapter) {
                if (rawProps.haikuCode) {
                    haikuAdapter = rawProps.haikuAdapter(rawProps.haikuCode);
                }
                else if (optionalRawBytecode) {
                    haikuAdapter = rawProps.haikuAdapter(optionalRawBytecode);
                }
                else {
                    throw new Error('A Haiku code object is required if you supply a Haiku adapter');
                }
            }
            else {
                // Otherwise default to the adapter which was initialized in the wrapper module
                haikuAdapter = haikuComponentFactory;
            }
            if (!haikuAdapter) {
                throw new Error('A Haiku adapter is required');
            }
            // Reuse existing mounted component if one exists
            if (!this.haiku) {
                this.haiku = haikuAdapter(// eslint-disable-line
                this.mount, haikuConfig);
            }
            else {
                // If the component already exists, update its options and make sure it remounts.
                // This action is important if we are in e.g. React Router.
                //
                // Important: Note that we should NOT call remount if we just initialized the instance (i.e. stanza above)
                // because we'll end up pausing the timelines before the first mount, resulting in a blank context.
                this.haiku.callRemount(haikuConfig);
            }
        };
        HaikuReactComponentInternal.prototype.buildHostElementPropsFromRawProps = function (rawProps) {
            var _this = this;
            var propsForReactHostElement = {};
            var _loop_1 = function (key) {
                if (willReactProbablyHandleProp(rawProps[key], key)) {
                    if (EventsDict_1.default[key]) {
                        // We wrap it because listeners expect to receive the Haiku component
                        // instance as an argument; and for convenience we provide the native
                        // event in addition to it and React's proxy
                        propsForReactHostElement[key] = function (proxy) {
                            return rawProps[key].call(_this, proxy, proxy && proxy.nativeEvent, _this.haiku);
                        };
                    }
                    else {
                        propsForReactHostElement[key] = rawProps[key];
                    }
                }
            };
            for (var key in rawProps) {
                _loop_1(key);
            }
            // Style objects are excluded above, but we definitely want it if provided
            var stylesForHostElement = rawProps.style || {};
            // Merge our basic host props with some defaults we want to assign
            return __assign({ id: this.state.randomId, style: __assign({ position: 'relative', margin: 0, padding: 0, border: 0, width: '100%', height: '100%', transform: 'matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)' }, stylesForHostElement) }, propsForReactHostElement);
        };
        HaikuReactComponentInternal.prototype.assignMountFromRef = function (element) {
            this.mount = element;
        };
        HaikuReactComponentInternal.prototype.render = function () {
            var _this = this;
            var hostElementProps = this.buildHostElementPropsFromRawProps(this.props);
            // Having this ref assigned like this is critical to the adapter working,
            // so we override it despite what the host element props might say
            hostElementProps.ref = function (element) {
                _this.assignMountFromRef(element);
            };
            return React.createElement(hostElementProps.tagName || DEFAULT_HOST_ELEMENT_TAG_NAME, hostElementProps);
        };
        HaikuReactComponentInternal.React = React;
        HaikuReactComponentInternal.ReactDOM = ReactDOM;
        // This setting is required to do proper setup for placeholder/inject vanities
        HaikuReactComponentInternal.isHaikuAdapter = true;
        return HaikuReactComponentInternal;
    }(React.Component));
    return HaikuReactComponentInternal;
}
exports.default = HaikuReactDOMAdapter;
function willReactProbablyHandleProp(prop, key) {
    // Exclude any 'complex' properties like objects
    if (prop && typeof prop === 'object') {
        return false;
    }
    // Assume functions, which are usually event listeners, are handled by Haiku
    if (typeof prop === 'function') {
        if (EventsDict_1.default[key]) {
            return true;
        }
        return false;
    }
    // Exclude props we know target Haiku config
    return !Config_1.DEFAULTS.hasOwnProperty(key);
}
function visit(el, visitor) {
    if (el) {
        visitor(el);
        if (el.children) {
            for (var i = 0; i < el.children.length; i++) {
                visit(el.children[i], visitor);
            }
        }
    }
}
function flexIdIfSame(virtual, dom) {
    if (virtual.attributes) {
        if (virtual.attributes['haiku-id']) {
            if (dom.getAttribute('haiku-id') === virtual.attributes['haiku-id']) {
                return virtual.attributes['haiku-id'];
            }
        }
        if (virtual.attributes.id) {
            if (dom.getAttribute('id') === virtual.attributes.id) {
                return virtual.attributes.id;
            }
        }
    }
    return null;
}
//# sourceMappingURL=HaikuReactDOMAdapter.js.map