"use strict";

const forEach = require("@sinonjs/commons").prototypes.array.forEach;

function walkInternal(obj, iterator, context, originalObj, seen) {
    let prop;
    const proto = Object.getPrototypeOf(obj);

    if (typeof Object.getOwnPropertyNames !== "function") {
        // We explicitly want to enumerate through all of the prototype's properties
        // in this case, therefore we deliberately leave out an own property check.
        /* eslint-disable-next-line guard-for-in */
        for (prop in obj) {
            iterator.call(context, obj[prop], prop, obj);
        }

        return;
    }

    forEach(Object.getOwnPropertyNames(obj), function (k) {
        if (seen[k] !== true) {
            seen[k] = true;
            const target =
                typeof Object.getOwnPropertyDescriptor(obj, k).get ===
                "function"
                    ? originalObj
                    : obj;
            iterator.call(context, k, target);
        }
    });

    if (proto) {
        walkInternal(proto, iterator, context, originalObj, seen);
    }
}

/* Walks the prototype chain of an object and iterates over every own property
 * name encountered. The iterator is called in the same fashion that Array.prototype.forEach
 * works, where it is passed the value, key, and own object as the 1st, 2nd, and 3rd positional
 * argument, respectively. In cases where Object.getOwnPropertyNames is not available, walk will
 * default to using a simple for..in loop.
 *
 * obj - The object to walk the prototype chain for.
 * iterator - The function to be called on each pass of the walk.
 * context - (Optional) When given, the iterator will be called with this object as the receiver.
 */
module.exports = function walk(obj, iterator, context) {
    return walkInternal(obj, iterator, context, obj, {});
};
