fus 2.4.1
import "./main" all

# `deepAssign` and `deepAbsorb` should use "merge" only when both source and target
# are non-array objects. It will look unnatural and weird to merge arrays.
#
# `deepAssign` and `deepAbsorb` must use deep clone when the source is a non-array object but
# can't be merged. If otherwise using direct assignment, then it will have severe
# side-effects: when sources are more than 1, the first or middle source's data
# may have been changed after the whole thing finishes.

isObject'export: (ns, x) ->
    js"
        typeof x === "object" || typeof x === "function"
    " and x ≠ null
isNormalObject'export: (ns, x) ->
    Object..isObject(x) and js"
        typeof x !== "function"
    " and not Array.isArray(x)

# TODO: need to use new concept
clone'export: (ns, x) ->
    y: {}
    Object.keys(x).forEach key ->
        y.(key): x.(key)
    y

keyValues'export: (ns, x) -> Object.keys(x).map(key -> [key, x.(key)])

deepAssign'export: (ns, theTarget, sources...) ->
    sources.forEach(theSource ->
        internalDeepAssign: (target, source) ->
            Object..keyValues(source).forEach([key, value] ->
                if Object..isObject(value) and not Array.isArray(value)
                and Object..isObject(target.(key)) and not Array.isArray(target.(key))
                    internalDeepAssign(target.(key), value)
                else
                    target.(key): Object..deepClone(value)
            )
        internalDeepAssign(theTarget, theSource)
    )
    theTarget

absorb'export: (ns, subject, objects...) ->
    objects.forEach(object ->
        Object..keyValues(object).forEach([key, value] ->
            subject.(key): value if subject.(key) = void
        )
    )
    subject

deepAbsorb'export: (ns, theSubject, objects...) ->
    objects.forEach(theObject ->
        internalDeepAbsorb: (subject, object) ->
            Object..keyValues(object).forEach([key, value] ->
                if Object..isObject(value) and not Array.isArray(value)
                and Object..isObject(subject.(key)) and not Array.isArray(subject.(key))
                    internalDeepAbsorb(subject.(key), value)
                else
                    subject.(key): Object..deepClone(value) if subject.(key) = void
            )
        internalDeepAbsorb(theSubject, theObject)
    )
    theSubject

deepClone'export: (ns, x) ->
    if Object..isObject(x)
        theTarget: Array.isArray(x) ? [] | {}
        deepCopyFrom: (target, source) ->
            Object..keyValues(source).forEach([key, value] ->
                if Object..isObject(value)
                    target.(key): Array.isArray(value) ? [] | {}
                    deepCopyFrom(target.(key), value)
                else
                    target.(key): value
            )
        deepCopyFrom(theTarget, x)
        theTarget
    else
        x
