var Hello = ({ dateTime }) => {
    return ['h1', {}, [`It is: ${dateTime}`]];
};

var App = () => {
    return ['div', { 'className': 'header' },
            [Hello({ dateTime: new Date() }),
             ['input', { 'type': 'submit', 'disabled': 'disabled' }, []]
            ]
           ];
}


var d =
['div', { 'className': 'header' },
  [['h1', {}, ['Hello']],
   ['input', { 'type': 'submit', 'disabled': 'disabled' }, []]
  ]
];
d =
['div', { 'className': 'header' },
  [['h1', {}, ['Hello, how have you been?']],
   ['input', { 'type': 'submit', 'style': 'color: red;' }, []]
  ]
];

var e = createElement(d);

render(e, $0)





var d =
['div', { 'className': 'header' },
  [['h1', {}, ['Hello']],
   ['input', { 'type': 'submit', 'disabled': 'disabled' }, []]
  ]
];

function createElement(node) {
    // an array: not text, number, or other primitive
    if (typeof node === 'object') {
        const [ tag, props, children ] = node;
        return {
            type: tag,
            props: {
                ...props,
                children: children.map(createElement)
            }
        };
    }

    // primitives like text or number
    return {
        type: 'TEXT',
        props: {
            nodeValue: node,
            children: []
        }
    };
}

let renderTrees = {};
function render(element, container) {
    const tree =
      render_internal(element, container, renderTrees[container]);
    // render complete, store the updated tree
    renderTrees[container] = tree;
}

function render_internal(element, container, prevElement) {
    let domElement, children;
    if (!element && prevElement) {
        removeDOMElement(prevElement);
        return;
    } else if (element && !prevElement) {
        domElement = createDOMElement(element);
    } else if (element.type === prevElement.type) {
        domElement = prevElement.domElement;
    } else {
        removeDOMElement(prevElement);
        domElement = createDOMElement(element);
    }
    setDOMProps(element, domElement, prevElement);
    children = renderChildren(element, domElement, prevElement);

    if (!prevElement || domElement !== prevElement.domElement) {
        container.appendChild(domElement);
    }

    return {
        domElement: domElement,
        parent: container,
        type: element.type,
        props: {
            ...element.props,
            children: children
        }
    };
}

function removeDOMElement(prevElement) {
    prevElement.parent.removeChild(prevElement.domElement);
}

function createDOMElement(element) {
    return element.type === 'TEXT' ?
      document.createTextNode(element.props.nodeValue) :
      document.createElement(element.type);
}

function setDOMProps(element, domElement, prevElement) {
    if (prevElement) {
        Object.keys(prevElement.props)
            .filter((key) => key !== 'children')
            .forEach((key) => {
                domElement[key] = '';
            });
    }
    Object.keys(element.props)
      .filter((key) => key !== 'children')
      .forEach((key) => {
          domElement[key] = element.props[key];
    });
}

function renderChildren(element, domElement, prevElement = { props: { children: [] }}) {
    const elementLen = element.props.children.length;
    const prevElementLen = prevElement.props.children.length;
    // remove now unused elements
    for (let i = elementLen; i < prevElementLen - elementLen; i++) {
        removeDOMElement(element.props.children[i]);
    }
    // render existing and new elements
    return element.props.children.map((child, i) => {
        const prevChild = i < prevElementLen ? prevElement.props.children[i] : undefined;
        return render_internal(child, domElement, prevChild);
    });
}

function defaultAreEqual(oldProps, newProps) {
    if (typeof oldProps !== 'object' || typeof newProps !== 'object') {
        return false;
    }

    const oldKeys = Object.keys(oldProps);
    const newKeys = Object.keys(newProps);

    if (oldKeys.length !== newKeys.length) {
        return false;
    }

    for (let i = 0; i < oldKeys.length; i++) {
        // Object.is - the comparison to note
        if (!oldProps.hasOwnProperty(newKeys[i]) ||
            !Object.is(oldProps[newKeys[i]], newProps[newKeys[i]])) {
            return false;
        }
    }

    return true;
}

function memo(component, areEqual = defaultAreEqual) {
    let oldProps = {};
    let lastResult = false;
    return (props) => {
        if (lastResult && areEqual(oldProps, props) {
            return lastResult;
        } else {
            lastResult = component(props);
            oldProps = Object.assign({}, props); // shallow copy
            return lastResult;
        }
    };
}

var Hello = memo(({ dateTime }) => {
    return ['h1', {}, [`It is: ${dateTime}`]];
});

