import * as React from "react";
import { ComponentProps } from "react";
import { InPortal } from "react-reverse-portal";

const LazyPortalTouched = Symbol("LazyPortalTouched");
const CurrentLazyPortal = Symbol("CurrentLazyPortal");

/** 
 * A `react-reverse-portal` `<InPortal>` that defers its first render until it is used by a mounted `<OutPortal>`.
 * 
 * Package requirements:
 * - "react-reverse-portal": "^2.1"
 */
export class LazyInPortal extends React.PureComponent<ComponentProps<typeof InPortal>> {
    touchNode(oldNode?: ComponentProps<typeof InPortal>["node"]) {
        const newNode = this.props.node;
        if (oldNode == newNode) return;
        if (oldNode && oldNode[CurrentLazyPortal] == this) {
            oldNode[CurrentLazyPortal] = null;
        }

        if (!newNode[LazyPortalTouched]) {
            const { mount } = newNode;
            newNode[LazyPortalTouched] = true;
            newNode.mount = (...args) => {
                const ret = mount(...args);
                if (newNode[CurrentLazyPortal]) {
                    (newNode[CurrentLazyPortal] as this).onOutputMounted();
                }
                return ret;
            }
        }
        newNode[CurrentLazyPortal] = this;
    }

    componentDidMount(): void {
        this.touchNode();
    }

    componentDidUpdate(prevProps: Readonly<ComponentProps<typeof InPortal>>) {
        this.touchNode(prevProps.node);
    }

    componentWillUnmount(): void {
        const node = this.props.node;
        if (node) {
            node[CurrentLazyPortal] = null;
        }
    }

    onOutputMounted() {
        if (!this._rendered) this.forceUpdate();
    }

    private _rendered = false;
    render() {
        const { node } = this.props;
        if (this._rendered || node.element.isConnected) {
            this._rendered = true;
            return <InPortal {...this.props} />
        }
        return null;
    }
}
