//################################################################################################
/** @file Plain Old Javascript Object Parser mixin for ish.js
 * @mixin ish.type.obj.pojo
 * @author Nick Campbell
 * @license MIT
 * @copyright 2014-2023, Nick Campbell (wrapper)
 */ //############################################################################################
/*global module, define, global */                              //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */                                     //# Enable max complexity warnings for JSHint
//<MIXIN>
(function (fnMaskedEvaler) {
    'use strict';

    //$z.type.obj.pojo('{ neek: false, camp: core.type.str.is("yep") }', null, { context: { core: $z }, check: true })
    function init(core) {
        //################################################################################################
        /** Collection of Plain Old Javascript Object (POJO) Parser-based functionality.
         * @namespace ish.type.obj.pojo
         * @ignore
         */ //############################################################################################
        core.oop.partial(core.type.obj, function (/*oProtected*/) {
            return {
                pojo: core.extend(
                    //#########
                    /** Parses the passed value as a string representing a Plain Old Javascript Object (POJO).
                     * @function ish.type.obj.pojo.!
                     * @param {string} x Value representing the POJO data to parse.
                     * @param {variant} [vDefaultVal=undefined] Value representing the default return value if parsing fails.
                     * @param {object} [oOptions] Value representing the following options:
                     *      @param {object|function} [oOptions.context=null] Value representing the context that exposes properties and functionality the passed value is parsed under.
                     *      @param {boolean|string[]} [oOptions.reject=["eval", "Function", ".prototype.", ".constructor", "function", "=>"]] Value representing if functions are to be rejected or array of strings representing the values that cause a POJO string to be rejected.
                     * @returns {object[]} Value representing the POJO data.
                     * @see {@link https://stackoverflow.com/a/543820/235704|StackOverflow}
                     */ //#####
                    function (x, vDefaultVal, oOptions) {
                        var oReturnVal = (arguments.length > 1 ? vDefaultVal : {}),
                            oArguments = {
                                //r: null,
                                //e: null,
                                js: x
                            }
                        ;

                        //#
                        oOptions = core.type.obj.mk(oOptions);

                        //# If we (seem) to be eval'ing a valid POJO run it in our fnMaskedEvaler, collecting the .r(esult) into our oReturnVal
                        if (core.type.pojo.check(x, oOptions.reject)) {
                            fnMaskedEvaler(
                                oArguments,
                                0,
                                core.type.obj.is(oOptions.context, true) ?
                                    { o: oOptions.context, k: core.type.obj.ownKeys(oOptions.context), c: '' } :
                                    null
                            );
                            oReturnVal = oArguments.r || oReturnVal;
                        }

                        return oReturnVal;
                    }, {
                        //#########
                        /** Determines if the passed value is a safe string representing a Plain Old Javascript Object (POJO).
                         * @$note This represents a "better than nothing" approach to checking the relative safety of the passed value. However, values should only be loaded from trusted sources.
                         * @function ish.type.obj.pojo.check
                         * @param {string} x Value representing the POJO data to check.
                         * @param {boolean|string[]} [vReject=["eval", "Function", ".prototype.", ".constructor", "function", "=>"]] Value representing if functions are to be rejected or array of strings representing the values that cause a POJO string to be rejected.
                         * @returns {object[]} Value representing if the POJO data is relatively safe.
                         */ //#####
                        check: function (x, vReject) {
                            var i,
                                bReturnVal = core.type.arr.is(vReject),
                                a_vReject = (bReturnVal ? vReject : [
                                    /eval(\/\*.*?\*\/)?(\/\/.*?)?\(/g, /Function(\/\*.*?\*\/)?(\/\/.*?)?\(/g,
                                    ".prototype.", ".constructor" //, ".call"
                                ])
                            ;

                            //# If the borrowed bReturnVal is indicating that vReject isn't an .arr and vReject also isn't false, then ensure that functions are a_vReject'd as well
                            if (!bReturnVal && vReject !== false) {
                                a_vReject = a_vReject.concat([/function(\/\*.*?\*\/)?(\/\/.*?)?\(/g, "=>"]);
                            }

                            //# Remove all whitespace from the passed x then reset our bReturnVal based on its value
                            x = core.type.str.mk(x).replace(/\s/g, "");
                            bReturnVal = (x && x[0] === "{" && x[x.length - 1] === "}");

                            //# If x seems valid thus far, traverse the a_vReject array, flipping our bReturnVal and falling from the loop if we find any a_vReject's
                            if (bReturnVal) {
                                for (i = 0; i < a_vReject.length; i++) {
                                    if (a_vReject[i] instanceof RegExp) {
                                        if (a_vReject[i].test(x)) {
                                            bReturnVal = false;
                                            break;
                                        }
                                    }
                                    else if (x.indexOf(a_vReject[i]) > -1) {
                                        bReturnVal = false;
                                        break;
                                    }
                                }
                            }

                            return bReturnVal;
                        } //# type.obj.pojo.check
                    }
                )
            };
        }); //# core.type.obj.pojo

        //# .fire the plugin's loaded event
        core.io.event.fire("ish.type.obj.pojo");

        //# Return core to allow for chaining
        return core;
    } //# init


    //# If we are running server-side
    //#     NOTE: Compliant with UMD, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
    //#     NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
    if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
        module.exports = init;
    }
    //# Else if we are running in an .amd environment, register as an anonymous module
    else if (typeof define === 'function' && define.amd) {
        define([], init);
    }
    //# Else we are running in the browser, so we need to setup the _document-based features
    else {
        return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
    }
}(
    //#
    //#     NOTE: The fnMaskedEvaler is placed here to limit its scope and local variables as narrowly as possible (hence the use of `arguments[x]`)
    //#     Based on code inspired by https://stackoverflow.com/a/543820/235704
    function /*fnMaskedEvaler*/(/* oData, i, oInjectData */) {
        //# Create the oMask under a SEAF to keep it's locals out of scope
        arguments[3] = (function /*setMask*/(oInjectData) {
            var j,
                oMask = {},
                oThis = this || {},
                bServerside = (typeof window === 'undefined'),
                a_sKeys = Object.keys(bServerside ? global : window || {})
            ;

            //# Also oMask the following reserved words
            a_sKeys = a_sKeys.concat(["arguments", "eval", "Function"]);

            //# oMask out the locally accessible objects (including i, oMask and a_sKeys)
            for (j = 0; j < a_sKeys.length; j++) {
                oMask[a_sKeys[j]] = undefined;
            }

            //# un-oMask the oThis-based variables
            a_sKeys = Object.keys(oThis);
            for (j = 0; j < a_sKeys.length; j++) {
                oMask[a_sKeys[j]] = oThis[a_sKeys[j]];
            }

            //# un-oMask the oInjectData.o-based variables
            a_sKeys = Object.keys(oInjectData || {});
            for (j = 0; j < a_sKeys.length; j++) {
                oMask[a_sKeys[j]] = oInjectData[a_sKeys[j]];
            }

            return oMask;
        }).call(this, arguments[2].o);

        //# Ensure the passed i (aka arguments[1]) is 0
        //#     NOTE: i (aka arguments[1]) must be passed in as 0 ("bad assignment")
        //arguments[1] = 0;

        //# Traverse the .js, processing each entry as we go
        //#     NOTE: We use a getto-version of a for loop below to keep JSHint happy and to limit the exposed local variables to `arguments` only
        //#     TODO: .call the New Function with `use strict` inline to avoid automatic access to the Global object, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply and fixes issues in setTimeout, see: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
        while (arguments[1] < arguments[0].js.length) {
            try {
                //eval(arguments[0].js[arguments[1]]);
                (new Function("with(this){" + arguments[0].js[arguments[1]] + "}")).call(arguments[3]);
            } catch (e) {
                //# An error occured fnEval'ing the current index, so .push undefined into this index's entry in .results and log the .errors
                arguments[0].results.push(undefined);
                arguments[0].errors.push({ index: arguments[1], error: e, js: arguments[0].js[arguments[1]] });
            }
            arguments[1]++;
        }

        //# Return the modified oData to the caller
        //#     NOTE: As this is modified byref there is no need to actually return oData
        //return arguments[0];
    } //# fnMaskedEvaler
));
//</MIXIN>