"use strict";

// ----------------------------------------------------------------------------

const { isObject, isNumber, isDictionary, isArray } = require ('./type')

// ----------------------------------------------------------------------------

const keys = Object.keys
    , values = (x) => ((!isArray (x)) ? Object.values (x) : x) // don't copy arrays if they're already arrays
    , index = (x) => new Set (values (x))
    , extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free
    , clone = (x) => (isArray (x) ? Array.from (x) : extend (x)) // clone arrays or objects

// ----------------------------------------------------------------------------

module.exports = {
    keys
    , values
    , extend
    , clone
    , index
    , ordered: (x) => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python)
    , unique: (x) => Array.from (index (x))
    , arrayConcat: (a, b) => a.concat (b)

    // ------------------------------------------------------------------------

    , inArray (needle, haystack) {

        return haystack.includes (needle)
    }

    , toArray (object) {

        return Object.values (object)
    }

    , isEmpty (object) {
        if (!object) {
            return true
        }
        return (Array.isArray (object) ? object : Object.keys (object)).length < 1;
    }

    // ------------------------------------------------------------------------

    , keysort (x, out = {}) {

        for (const k of keys (x).sort ())
            out[k] = x[k]

        return out
    }

    // ------------------------------------------------------------------------

    /*
        Accepts a map/array of objects and a key name to be used as an index:
        array = [
            { someKey: 'value1', anotherKey: 'anotherValue1' },
            { someKey: 'value2', anotherKey: 'anotherValue2' },
            { someKey: 'value3', anotherKey: 'anotherValue3' },
        ]
        key = 'someKey'

        Returns a map:
        {
            value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
            value2: { someKey: 'value2', anotherKey: 'anotherValue2' },
            value3: { someKey: 'value3', anotherKey: 'anotherValue3' },
        }
    */

    , indexBy (x, k, out = {}) {

        for (const v of values (x)) {
            if (k in v) {
                out[v[k]] = v
            }
        }

        return out
    }

    // ------------------------------------------------------------------------

    /*
        Accepts a map/array of objects and a key name to be used as a grouping parameter:
        array = [
            { someKey: 'value1', anotherKey: 'anotherValue1' },
            { someKey: 'value1', anotherKey: 'anotherValue2' },
            { someKey: 'value3', anotherKey: 'anotherValue3' },
        ]
        key = 'someKey'

        Returns a map:
        {
            value1: [
                { someKey: 'value1', anotherKey: 'anotherValue1' },
                { someKey: 'value1', anotherKey: 'anotherValue2' },
            ]
            value3: [
                { someKey: 'value3', anotherKey: 'anotherValue3' }
            ],
        }
    */

    , groupBy (x, k, out = {}) {

        for (const v of values (x)) {
            if (k in v) {
                const p = v[k]
                out[p] = out[p] || []
                out[p].push (v)
            }
        }
        return out
    }

    // ------------------------------------------------------------------------

    /*
        Accepts a map/array of objects, a key name and a key value to be used as a filter:
        array = [
            { someKey: 'value1', anotherKey: 'anotherValue1' },
            { someKey: 'value2', anotherKey: 'anotherValue2' },
            { someKey: 'value3', anotherKey: 'anotherValue3' },
        ]
        key = 'someKey'
        value = 'value1'

        Returns an array:
        [
            value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
        ]
    */

    , filterBy (x, k, value = undefined, out = []) {

        for (const v of values (x)) {
            if (v[k] === value) {
                out.push (v)
            }
        }

        return out
    }

    // ------------------------------------------------------------------------
    // NB: MUTATES ARRAY!

    , sortBy: (array, key, descending = false, direction  = descending ? -1 : 1) => array.sort ((a, b) => {
        if (a[key] < b[key]) {
            return -direction
        } else if (a[key] > b[key]) {
            return direction
        } else {
            return 0
        }
    })

    // ------------------------------------------------------------------------

    , flatten: function flatten (x, out = []) {

        for (const v of x) {
            if (isArray (v)) {
                flatten (v, out)
            } else {
                out.push (v)
            }
        }

        return out
    }

    // ------------------------------------------------------------------------

    , pluck: (x, k) => values (x).filter ((v) => k in v).map ((v) => v[k])

    // ------------------------------------------------------------------------

    , omit (x, ...args) {

        if (!Array.isArray (x)) {

            const out = clone (x)

            for (const k of args) {
                if (isArray (k)) { // omit (x, ['a', 'b'])
                    for (const kk of k) {
                        delete out[kk]
                    }
                } else {
                    delete out[k] // omit (x, 'a', 'b')
                }
            }

            return out
        }

        return x
    }

    // ------------------------------------------------------------------------

    , sum (...xs) {

        const ns = xs.filter (isNumber) // leave only numbers

        return (ns.length > 0) ? ns.reduce ((a, b) => a + b, 0) : undefined
    }

    // ------------------------------------------------------------------------

    , deepExtend: function deepExtend (...xs) {
        let out = undefined
        for (const x of xs) {
            if (isDictionary (x)) {
                if (!isDictionary (out)) {
                    out = {}
                }
                for (const k in x) {
                    out[k] = deepExtend (out[k], x[k])
                }
            } else {
                out = x
            }
        }
        return out
    }

// ----------------------------------------------------------------------------

}
