"use strict";
/**
 * Copyright (c) Haiku 2016-2018. All rights reserved.
 */
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 addLegacyOriginSupport_1 = require("./helpers/addLegacyOriginSupport");
var compareSemver_1 = require("./helpers/compareSemver");
var visitManaTree_1 = require("./helpers/visitManaTree");
var xmlToMana_1 = require("./helpers/xmlToMana");
var schema_1 = require("./properties/dom/schema");
var functionToRFO_1 = require("./reflection/functionToRFO");
var reifyRFO_1 = require("./reflection/reifyRFO");
var STRING_TYPE = 'string';
var UpgradeVersionRequirement;
(function (UpgradeVersionRequirement) {
    UpgradeVersionRequirement["OriginSupport"] = "3.2.0";
    UpgradeVersionRequirement["TimelineDefaultFrames"] = "3.2.23";
})(UpgradeVersionRequirement || (UpgradeVersionRequirement = {}));
var HAIKU_SOURCE_ATTRIBUTE = 'haiku-source';
var HAIKU_VAR_ATTRIBUTE = 'haiku-var';
var requiresUpgrade = function (coreVersion, requiredVersion) { return !coreVersion ||
    compareSemver_1.default(coreVersion, requiredVersion) < 0; };
/**
 * @function run
 * @description Mechanism to modify our bytecode from legacy format to the current format.
 * Think of this like a migration that always runs in production components just in case we
 * get something that happens to be legacy.
 */
exports.runMigrations = function (component, options, version) {
    var bytecode = component.bytecode;
    if (!bytecode.states) {
        bytecode.states = {};
    }
    if (!bytecode.metadata) {
        bytecode.metadata = {};
    }
    // Convert the properties array to the states dictionary
    if (bytecode.properties) {
        var properties = bytecode.properties;
        delete bytecode.properties;
        for (var i = 0; i < properties.length; i++) {
            var propertySpec = properties[i];
            var updatedSpec = {};
            if (propertySpec.value !== undefined) {
                updatedSpec.value = propertySpec.value;
            }
            if (propertySpec.type !== undefined) {
                updatedSpec.type = propertySpec.type;
            }
            if (propertySpec.setter !== undefined) {
                updatedSpec.set = propertySpec.setter;
            }
            if (propertySpec.getter !== undefined) {
                updatedSpec.get = propertySpec.getter;
            }
            if (propertySpec.set !== undefined) {
                updatedSpec.set = propertySpec.set;
            }
            if (propertySpec.get !== undefined) {
                updatedSpec.get = propertySpec.get;
            }
            bytecode.states[propertySpec.name] = updatedSpec;
        }
    }
    // Convert the eventHandlers array into a dictionary
    // [{selector:'foo',name:'onclick',handler:function}] => {'foo':{'onclick':{handler:function}}}
    if (Array.isArray(bytecode.eventHandlers)) {
        var eventHandlers = bytecode.eventHandlers;
        delete bytecode.eventHandlers;
        bytecode.eventHandlers = {};
        for (var j = 0; j < eventHandlers.length; j++) {
            var eventHandlerSpec = eventHandlers[j];
            if (!bytecode.eventHandlers[eventHandlerSpec.selector]) {
                bytecode.eventHandlers[eventHandlerSpec.selector] = {};
            }
            bytecode.eventHandlers[eventHandlerSpec.selector][eventHandlerSpec.name] = {
                handler: eventHandlerSpec.handler,
            };
        }
    }
    // Convert a string template into our internal object format
    if (typeof bytecode.template === STRING_TYPE) {
        bytecode.template = xmlToMana_1.default(bytecode.template);
    }
    // If specified, make sure that internal URL references, e.g. url(#my-filter), are unique
    // per each component instance, otherwise we will get filter collisions and weirdness on the page
    var referencesToUpdate = {};
    var alreadyUpdatedReferences = {};
    if (bytecode.template) {
        visitManaTree_1.default('0', bytecode.template, function (elementName, attributes, children, node) {
            if (options && options.referenceUniqueness) {
                if (elementName === 'filter' ||
                    elementName === 'filterGradient') {
                    if (attributes.id && !alreadyUpdatedReferences[attributes.id]) {
                        var prev = attributes.id;
                        var next = prev + '-' + options.referenceUniqueness;
                        attributes.id = next;
                        referencesToUpdate['url(#' + prev + ')'] = 'url(#' + next + ')';
                        alreadyUpdatedReferences[attributes.id] = true;
                    }
                }
            }
            // Switch the legacy 'source' attribute to the new 'haiku-source'
            if (attributes && attributes.source) {
                attributes[HAIKU_SOURCE_ATTRIBUTE] = attributes.source;
                delete attributes.source;
            }
            if (attributes && attributes.identifier) {
                attributes[HAIKU_VAR_ATTRIBUTE] = attributes.identifier;
                delete attributes.identifier;
            }
        }, null, 0);
    }
    if (bytecode.timelines) {
        for (var timelineName in bytecode.timelines) {
            for (var selector in bytecode.timelines[timelineName]) {
                // Legacy backgroundColor was a root prop; in newer versions it's style.backgroundColor.
                // We only want to update this if the user *hasn't* explicitly set style.backroundColor.
                if (bytecode.timelines[timelineName][selector].backgroundColor &&
                    !bytecode.timelines[timelineName][selector]['style.backgroundColor']) {
                    bytecode.timelines[timelineName][selector]['style.backgroundColor'] =
                        bytecode.timelines[timelineName][selector].backgroundColor;
                    delete bytecode.timelines[timelineName][selector].backgroundColor;
                    continue;
                }
                // If we're a filter attribute, update our references per those to whom uniqueness was added above.
                // This appends a "*-abc123" string to the filter to avoid collisions when multiple same components
                // are mounted in a single web page
                if (bytecode.timelines[timelineName][selector].filter) {
                    for (var keyframeMs in bytecode.timelines[timelineName][selector].filter) {
                        var keyframeDesc = bytecode.timelines[timelineName][selector].filter[keyframeMs];
                        if (keyframeDesc && referencesToUpdate[keyframeDesc.value]) {
                            keyframeDesc.value = referencesToUpdate[keyframeDesc.value];
                        }
                    }
                }
                // The fill attribute may reference a <filterGradient> property; avoid collisions same as above.
                if (bytecode.timelines[timelineName][selector].fill) {
                    for (var keyframeMs in bytecode.timelines[timelineName][selector].fill) {
                        var keyframeDesc = bytecode.timelines[timelineName][selector].fill[keyframeMs];
                        if (keyframeDesc && referencesToUpdate[keyframeDesc.value]) {
                            keyframeDesc.value = referencesToUpdate[keyframeDesc.value];
                        }
                    }
                }
            }
        }
    }
    var coreVersion = bytecode.metadata.core || bytecode.metadata.player;
    if (requiresUpgrade(coreVersion, "3.2.0" /* OriginSupport */)) {
        component.visit(function (element) {
            if (schema_1.default[element.tagName] && schema_1.default[element.tagName]['origin.x']) {
                // We only need to upgrade elements whose schemas support origin.
                addLegacyOriginSupport_1.default(element.tagName === 'svg', element.rawLayout, ((element.parent && element.parent.layout && element.parent.layout.size) ||
                    { x: 0, y: 0, z: 0 }), bytecode.timelines.Default["haiku:" + element.getComponentId()]);
            }
        });
        // Bust caches; we only rendered to populate our layout stubs.
        component.clearCaches();
        component.markForFullFlush();
    }
    if (requiresUpgrade(coreVersion, "3.2.23" /* TimelineDefaultFrames */)) {
        var wereAnyEventHandlersUpgraded_1 = false;
        component.eachEventHandler(function (eventSelector, eventName, _a) {
            var original = _a.original;
            var rfo = functionToRFO_1.default(original);
            var body = rfo.__function.body;
            var changed = false;
            ['.seek(', '.gotoAndPlay(', '.gotoAndStop('].forEach(function (methodSignature) {
                for (var cursor = 0; cursor < body.length; ++cursor) {
                    if (body.substring(cursor, cursor + methodSignature.length) !== methodSignature) {
                        continue;
                    }
                    // We have matched e.g. this.getDefaultTimeline().seek( at the string index of ".seek(".
                    // Using the assumption that the method arguments do not contain string arguments with parentheses inside,
                    // we can apply a simple parenthesis-balancing algorithm here.
                    wereAnyEventHandlersUpgraded_1 = changed = true;
                    cursor += methodSignature.length;
                    var openParens = 1;
                    while (openParens > 0 && cursor < body.length) {
                        if (body[cursor] === '(') {
                            openParens++;
                        }
                        else if (body[cursor] === ')') {
                            openParens--;
                        }
                        ++cursor;
                    }
                    // Essentially, replace .seek(foo) with .seek(foo, 'ms').
                    body = body.slice(0, cursor - 1) + ", 'ms')" + body.slice(cursor);
                }
            });
            if (changed) {
                bytecode.eventHandlers[eventSelector][eventName].handler = reifyRFO_1.default(__assign({}, rfo.__function, { body: body }));
                delete bytecode.eventHandlers[eventSelector][eventName].original;
            }
        });
        if (wereAnyEventHandlersUpgraded_1) {
            component.bindEventHandlers();
        }
    }
    // Ensure the bytecode metadata core version is recent.
    bytecode.metadata.core = version;
};
//# sourceMappingURL=Migration.js.map