<div id="app"></div>
<script type="module">

import { h, Component, render } from 'https://unpkg.com/preact?module';

var N = {
    Create:(inMeta, ...inChildren) =>
    {
        var output = {
            Meta:inMeta,

            Children:[],
            Parents:[],
            /*
            WalkID:0,

            GetUpward:[],
            GetDownward:[],
            GetOutside:[],

            SetUpward:[],
            SetDownward:[],
            SetOutside:[]
            */
        };
        inChildren.forEach( inChild => N.Connect(output, "Children", inChild, "Parents") );
        return output;
    },
    Connect:(inParent, inParentRefs, inChild, inChildRefs) =>
    {
        inParent[inParentRefs].push(inChild);
        inChild[inChildRefs].push(inParent);
    },
    Disconnect:(inParent, inParentRefs, inChild, inChildRefs) =>
    {
        let checkRemove = (inArray, inMember) =>
        {
            inArray.findIndex( (inMember, inIndex, inArray) => (inMember === match) ? inArray.splice(inIndex, 1) : false );
        };
        checkRemove(inParent[inParentRefs], inChild);
        checkRemove(inChild[inChildRefs], inParent);
    },
    Walk:(inNode, inKey, inIterator, inWalkID) =>
    {
        let array = inNode[inKey];
        for(let i=0; i<array.length; i++)
        {
            let next = array[i];
            if(next.WalkID !== inWalkID)
            {
                next.WalkID = inWalkID;
                let results = inIterator(next);
                if(results !== false)
                {
                    N.Walk(next, inKey, inIterator, inWalkID);
                }
            }
        }
    },
    Modify(inNode)
    {
        inNode.WalkID = "tweak-"+Math.random();

        let leaves = [];
        let gatherUp = n => N.Connect(inNode, "SetUpward", n, "GetUpward");
        let gatherDown = n =>
        {
            N.Connect(inNode, "SetDownward", n, "GetDownward");
            n.Children.length == 0 ?  leaves.push(n) : null;
        };
        let gatherOut = n => N.Connect(inNode, "SetOutside", n, "GetOutside");

        N.Walk(inNode, "Parents", gatherUp, inNode.WalkID);
        N.Connect(inNode, "SetDownward", inNode, "GetDownward");
        N.Walk(inNode, "Children", gatherDown, inNode.WalkID);
        leaves.forEach(leaf=>N.Walk(leaf, "Parents", gatherOut, inNode.WalkID));
    }
    // delete modify
    // create pivot
    // delete pivot
};

let Leafify = inRows => inRows.map(r => N.Create({Row:r}));
let Pivot = (inParent, inColumnIndicies, inSumIndicies, inDepth) =>
{
    let depth = inDepth||0;
    let uniques = {};
    let columnIndex = inColumnIndicies[depth];
    
    inParent.Meta.Leaves.forEach((inLeaf)=>
    {
        let row = inLeaf.Meta.Row;
        let value = row[columnIndex];
        let match = uniques[value];
        if(!match)
        {
            match = uniques[value] = {Label:value, Row:[...row], Leaves:[]};
            N.Connect(inParent, "Children", N.Create(match), "Parents");
        }
        else
        {
            inSumIndicies.forEach(inIndex => match.Row[inIndex] += row[inIndex]);
        }
        match.Leaves.push(inLeaf);
    });

    delete inParent.Meta.Leaves;
    if(depth == inColumnIndicies.length-1)
    {
        // cant go any deeper
        inParent.Children.forEach( inChild =>
        {
            inChild.Meta.Leaves.forEach( inLeaf => N.Connect(inChild, "Children", inLeaf, "Parents") );
            delete inChild.Meta.Leaves;
        });
    }
    else
    {
        inParent.Children.forEach( child => Pivot(child, inColumnIndicies, inSumIndicies, depth+1) );
    }
};
let csv = Leafify([
    ["#1", "a", "long",  1],
    ["#2", "b", "long",  2],
    ["#3", "b", "short", 2],
    ["#4", "a", "long",  3],
    ["#5", "b", "short", 1],
    ["#6", "a", "short", 0],
    ["#7", "b", "short", 7],
]);
let pivotRoot = N.Create({Leaves:csv});
Pivot(pivotRoot, [1, 2], [3]);
console.log(pivotRoot);

let ElNode = ({node}) =>
{
    var children = [];
    var table = [];
    if(node.Children.length)
    {
        if(node.Meta.Row)
        {
            table = node.Meta.Row.map( cell => h("span", {style:{padding:"10px"}}, cell));
        }
        children = [
            h("strong", null, node.Meta.Label||"a node"),
            ...table,
            ...node.Children.map( inChild => h(ElNode, {node:inChild}))
        ];
    }
    else
    {
        children = [
            h("strong", null, node.Meta.Label||"a node"),
            ...node.Meta.Row.map( cell => h("span", {style:{padding:"10px"}}, cell))
        ];
    }
    return h("div", {style:{padding:"10px"}}, children);
}
let ElRoot = ({root}) =>
{
    return h("div", null, [
        h("h3", null, "tree view"),
        h(ElNode, {node:root})
    ])
};
render(h(ElRoot, {root:pivotRoot}, null), document.querySelector("#app"));

</script>
<!--=
<script>
let tree1 = N.Create("root1",
    N.Create("branch1", 
        N.Create("leaf1"),
        N.Create("leaf2"),
        N.Create("leaf3"),
    ),
    N.Create("branch2",
        N.Create("leaft3"),
        N.Create("leaft4")
    )
);

let leaves = [];
let leavesCollect = n =>
{
    if(n.Children.length == 0)
    {
        leaves.push(n);
    }
};
N.Walk(tree1, "Children", leavesCollect, 1);

let tree2 = N.Create("root2", 
    N.Create("branch3",
        N.Create("leaf5"),
        N.Create("leaf6")
    ),
    N.Create("branch4", ...leaves)
);

let orchard = N.Create("orchard", tree1, tree2);


/*************************************************************/

N.Modify(tree1);
console.log(tree1);

</script>
-->