ko.observableArray = initialValues => {
    initialValues = initialValues || [];

    if (typeof initialValues != 'object' || !('length' in initialValues))
        throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");

    var result = ko.observable(initialValues);
    Object.setPrototypeOf(result, ko.observableArray['fn']);
    return result.extend({'trackArrayChanges':true});
};

ko.observableArray['fn'] = {
    'remove': function (valueOrPredicate) {
        var underlyingArray = this.peek();
        var removed = false;
        var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate)
            ? valueOrPredicate : value => value === valueOrPredicate;
        var i = underlyingArray.length;
        while (i--) {
            var value = underlyingArray[i];
            if (predicate(value)) {
                if (underlyingArray[i] !== value) {
                    throw Error("Array modified during remove; cannot remove item");
                }
                removed || this.valueWillMutate();
                removed = true;
                underlyingArray.splice(i, 1);
            }
        }
        removed && this.valueHasMutated();
    }
};

// Note that for browsers that don't support proto assignment, the
// inheritance chain is created manually in the ko.observableArray constructor
Object.setPrototypeOf(ko.observableArray['fn'], ko.observable['fn']);

// Populate ko.observableArray.fn with native arrays functions
Object.getOwnPropertyNames(Array.prototype).forEach(methodName => {
    // skip property length
    if (typeof Array.prototype[methodName] === 'function' && 'constructor' != methodName) {
        if (["copyWithin", "fill", "pop", "push", "reverse", "shift", "sort", "splice", "unshift"].includes(methodName)) {
            // Mutator methods
            // https://developer.mozilla.org/tr/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#mutator_methods
            // Important: Do not add any additional functions here that may reasonably be used to *read* data from the array
            // because we'll eval them without causing subscriptions, so ko.computed output could end up getting stale
            ko.observableArray['fn'][methodName] = function (...args) {
                // Use "peek" to avoid creating a subscription in any computed that we're executing in the context of
                // (for consistency with mutating regular observables)
                var underlyingArray = this.peek();
                this.valueWillMutate();
                this.cacheDiffForKnownOperation(underlyingArray, methodName, args);
                var methodCallResult = underlyingArray[methodName](...args);
                this.valueHasMutated();
                // The native sort and reverse methods return a reference to the array, but it makes more sense to return the observable array instead.
                return methodCallResult === underlyingArray ? this : methodCallResult;
            };
        } else {
            // Accessor and Iteration methods
            ko.observableArray['fn'][methodName] = function (...args) {
                return this()[methodName](...args);
            };
        }
    }
});

ko.isObservableArray = instance => {
    return ko.isObservable(instance)
        && typeof instance["remove"] == "function"
        && typeof instance["push"] == "function";
};

ko.exportSymbol('observableArray', ko.observableArray);
ko.exportSymbol('isObservableArray', ko.isObservableArray);
