<commit_before>import Cascade from '../cascade/Cascade';
import Computed from '../graph/Computed';
import VirtualNode from './VirtualNode';
import ComponentNode from './ComponentNode';
import { IVirtualNode, IVirtualNodeProps } from './IVirtualNode';
import Diff, { DiffOperation } from '../util/Diff';

var componentContexts: Component<IVirtualNodeProps>[][] = [];
var context: Component<IVirtualNodeProps>[] = undefined;

export abstract class Component<T extends IVirtualNodeProps> implements IVirtualNode<T> {
    // TODO: Remove unused uniqueId?
    uniqueId: number;
    props: T;
    children: any;
    key: string;
    root: any;
    element: Node;
    context: Component<IVirtualNodeProps>[];
    rendered: boolean = false;

    constructor(props?: T, ...children: any[]) {
        // TODO: Remove unused uniqueId?
        this.uniqueId = Math.floor(Math.random() * 1000000);
        this.props = props || ({} as any);
        this.key = this.props.key;
        // TODO: Remove key and ref?
        // if (this.props.key) {
        // delete this.props.key;
        // }
        this.children = children ? VirtualNode.fixChildrenArrays(children) : [];
    }

    init() {
        // This should subscribe to all observables used by render.
        Cascade.createComputed(this, 'root', () => {
            // Dispose of old context
            this.disposeContext();

            // Push this to the current context
            if (context) {
                context.push(this);
            }

            // Create a new context
            Component.pushContext();

            // Render
            var root = this.render();

            // Store the new context
            this.context = Component.popContext();
            return root;
        });
        // Only update if we are re-rendering
        Cascade.subscribe(this, 'root', (root: any, oldRoot: any) => {
            if (this.rendered) {
                var element = this.element;
                this.toNode(oldRoot);
                if (element !== this.element) {
                    if (element) {
                        var parentNode = element.parentNode;
                        if (parentNode) {
                            if (this.element) {
                                parentNode.replaceChild(this.element, element);
                            } else {
                                parentNode.removeChild(element);
                            }
                        }
                    }
                }
            }
        });
    }

    abstract render(): any;

    toNode(oldRoot?: any): Node {
        var root = Cascade.peek(this, 'root');
        var oldElement = this.element;

        var element: Node;
        var rootType = typeof root;

        var noDispose: boolean = false;

        // Test if we must diff.
        // If we have an old root, we may need to diff.
        if (oldRoot && typeof oldRoot === rootType) {
            switch (rootType) {
                case 'string':
                    if (root !== oldRoot) {
                        element = document.createTextNode(root as string);
                    } else {
                        element = oldElement;
                    }
                    break;
                case 'object':
                    if (root) {
                        if (root instanceof Component) {
                            // Root is a Component
                            if (root.constructor === oldRoot.constructor && root.key === oldRoot.key) {
                                // Root and OldRoot are both the same Component - Diff this case
                                root.element = oldElement;
                                element = this.diffComponents(root, oldRoot, oldElement);
                                noDispose = true;
                            } else {
                                // Root is a different Component
                                element = root.toNode();
                            }
                        } else if (root.type) {
                            // Root is a VirtualNode
                            if (root.type === oldRoot.type && root.key === oldRoot.key) {
                                // Root and OldRoot are both the same VirtualNode - Diff this case
                                root.element = oldElement;
                                element = this.diffVirtualNodes(root, oldRoot, oldElement as HTMLElement);
                            } else {
                                // Root is a different VirtualNode
                                element = root.toNode();
                            }
                        } else {
                            // Root is an Object
                            element = document.createTextNode(root.toString());
                        }
                        // Ignore Null
                    }
                    break;
                case 'undefined':
                    // Ignore Undefined    
                    break;
                // Number and anything else    
                // case 'number':
                default:
                    if (root !== oldRoot) {
                        // Render to a string
                        element = document.createTextNode(root.toString());
                    } else {
                        // Root and OldRoot are the same
                        element = oldElement;
                    }
                    break;
            }
        } else {
            switch (rootType) {
                case 'string':
                    element = document.createTextNode(root as string);
                    break;
                case 'object':
                    if (root) {
                        if (root.toNode) {
                            element = root.toNode();
                        } else {
                            element = document.createTextNode(root.toString());
                        }
                    }
                    // Ignore Null
                    break;
                case 'undefined':
                    // Ignore Undefined    
                    break;
                // Number and anything else
                // case 'number':
                default:
                    element = document.createTextNode(root.toString());
                    break;
            }
        }

        if (this.props && this.props.ref) {
            this.props.ref(element);
        }

        if (!noDispose && oldRoot && oldRoot instanceof Component) {
            //oldRoot.dispose();
        }

        this.afterRender(element, this.rendered);

        if (!element) {
            element = document.createComment('Empty Component');
        }
        this.element = element;
        this.rendered = true;
        return element;
    }

    dispose() {
        var computed = Cascade.getObservable(this, 'root') as Computed<any>;
        computed.dispose(true);
        if (this.context) {
            for (var index = 0, length = this.context.length; index < length; index++) {
                let component = this.context[index];
                var computed = Cascade.getObservable(component, 'root') as Computed<any>;
                computed.dispose();
                component.dispose();
            }
        }
        this.afterDispose(this.element);
    }

    disposeContext() {
        if (this.context) {
            for (var index = 0, length = this.context.length; index < length; index++) {
                let component = this.context[index];
                var computed = Cascade.getObservable(component, 'root') as Computed<any>;
                computed.dispose();<commit_msg>Fixed Component root dispose recursion.<commit_after>import Cascade from '../cascade/Cascade';
import Computed from '../graph/Computed';
import VirtualNode from './VirtualNode';
import ComponentNode from './ComponentNode';
import { IVirtualNode, IVirtualNodeProps } from './IVirtualNode';
import Diff, { DiffOperation } from '../util/Diff';

var componentContexts: Component<IVirtualNodeProps>[][] = [];
var context: Component<IVirtualNodeProps>[] = undefined;

export abstract class Component<T extends IVirtualNodeProps> implements IVirtualNode<T> {
    // TODO: Remove unused uniqueId?
    uniqueId: number;
    props: T;
    children: any;
    key: string;
    root: any;
    element: Node;
    context: Component<IVirtualNodeProps>[];
    rendered: boolean = false;

    constructor(props?: T, ...children: any[]) {
        // TODO: Remove unused uniqueId?
        this.uniqueId = Math.floor(Math.random() * 1000000);
        this.props = props || ({} as any);
        this.key = this.props.key;
        // TODO: Remove key and ref?
        // if (this.props.key) {
        // delete this.props.key;
        // }
        this.children = children ? VirtualNode.fixChildrenArrays(children) : [];
    }

    init() {
        // This should subscribe to all observables used by render.
        Cascade.createComputed(this, 'root', () => {
            // Dispose of old context
            this.disposeContext();

            // Push this to the current context
            if (context) {
                context.push(this);
            }

            // Create a new context
            Component.pushContext();

            // Render
            var root = this.render();

            // Store the new context
            this.context = Component.popContext();
            return root;
        });
        // Only update if we are re-rendering
        Cascade.subscribe(this, 'root', (root: any, oldRoot: any) => {
            if (this.rendered) {
                var element = this.element;
                this.toNode(oldRoot);
                if (element !== this.element) {
                    if (element) {
                        var parentNode = element.parentNode;
                        if (parentNode) {
                            if (this.element) {
                                parentNode.replaceChild(this.element, element);
                            } else {
                                parentNode.removeChild(element);
                            }
                        }
                    }
                }
            }
        });
    }

    abstract render(): any;

    toNode(oldRoot?: any): Node {
        var root = Cascade.peek(this, 'root');
        var oldElement = this.element;

        var element: Node;
        var rootType = typeof root;

        var noDispose: boolean = false;

        // Test if we must diff.
        // If we have an old root, we may need to diff.
        if (oldRoot && typeof oldRoot === rootType) {
            switch (rootType) {
                case 'string':
                    if (root !== oldRoot) {
                        element = document.createTextNode(root as string);
                    } else {
                        element = oldElement;
                    }
                    break;
                case 'object':
                    if (root) {
                        if (root instanceof Component) {
                            // Root is a Component
                            if (root.constructor === oldRoot.constructor && root.key === oldRoot.key) {
                                // Root and OldRoot are both the same Component - Diff this case
                                root.element = oldElement;
                                element = this.diffComponents(root, oldRoot, oldElement);
                                noDispose = true;
                            } else {
                                // Root is a different Component
                                element = root.toNode();
                            }
                        } else if (root.type) {
                            // Root is a VirtualNode
                            if (root.type === oldRoot.type && root.key === oldRoot.key) {
                                // Root and OldRoot are both the same VirtualNode - Diff this case
                                root.element = oldElement;
                                element = this.diffVirtualNodes(root, oldRoot, oldElement as HTMLElement);
                            } else {
                                // Root is a different VirtualNode
                                element = root.toNode();
                            }
                        } else {
                            // Root is an Object
                            element = document.createTextNode(root.toString());
                        }
                        // Ignore Null
                    }
                    break;
                case 'undefined':
                    // Ignore Undefined    
                    break;
                // Number and anything else    
                // case 'number':
                default:
                    if (root !== oldRoot) {
                        // Render to a string
                        element = document.createTextNode(root.toString());
                    } else {
                        // Root and OldRoot are the same
                        element = oldElement;
                    }
                    break;
            }
        } else {
            switch (rootType) {
                case 'string':
                    element = document.createTextNode(root as string);
                    break;
                case 'object':
                    if (root) {
                        if (root.toNode) {
                            element = root.toNode();
                        } else {
                            element = document.createTextNode(root.toString());
                        }
                    }
                    // Ignore Null
                    break;
                case 'undefined':
                    // Ignore Undefined    
                    break;
                // Number and anything else
                // case 'number':
                default:
                    element = document.createTextNode(root.toString());
                    break;
            }
        }

        if (this.props && this.props.ref) {
            this.props.ref(element);
        }

        if (!noDispose && oldRoot && oldRoot instanceof Component) {
            //oldRoot.dispose();
        }

        this.afterRender(element, this.rendered);

        if (!element) {
            element = document.createComment('Empty Component');
        }
        this.element = element;
        this.rendered = true;
        return element;
    }

    dispose() {
        var computed = Cascade.getObservable(this, 'root') as Computed<any>;
        computed.dispose();
        if (this.context) {
            for (var index = 0, length = this.context.length; index < length; index++) {
                let component = this.context[index];
                var computed = Cascade.getObservable(component, 'root') as Computed<any>;
                computed.dispose();
                component.dispose();
            }
        }
        this.afterDispose(this.element);
    }

    disposeContext() {
        if (this.context) {
            for (var index = 0, length = this.context.length; index < length; index++) {
                let component = this.context[index];
                var computed = Cascade.getObservable(component, 'root') as Computed<any>;
                computed.dispose();
{'fraction_non_alphanumeric': 0.09552529182879377, 'fraction_numerical': 0.0011673151750972762, 'mean_word_length': 1.0906995661605206, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5189667', 'n_tokens_mistral': 3625, 'n_tokens_neox': 3418, 'n_words': 939}