import { ClassDB, Node, type NodePathMap } from "godot"
import ReactReconciler from "react-reconciler"
import { camelToSnake, hasProperty, snakeToPascal } from "./utils.js"


const DefaultEventPriority = 32

export const make = () => {
    let eventTime = 0
    let currentPriority = DefaultEventPriority

    return ReactReconciler<
        string,
        Record<string, unknown>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        Node<NodePathMap>,
        unknown,
        unknown,
        unknown,
        unknown,
        unknown
    >({
        supportsMutation: true,

        createInstance(type, props) {
            let instance: Node
            if (type === "custom") {
                if (!hasProperty(props, "class"))
                    throw new Error("Property 'class' required when using the 'custom' intrinsic type")
                instance = new (props.class as any)()
            }
            else {
                const className = snakeToPascal(camelToSnake(type))
                if (!ClassDB.class_exists(className))
                    throw new Error(`Class is invalid: '${className}' (declared as '${type}') is not a valid engine or GDExtension class`)
                instance = ClassDB.instantiate(className)
            }

            if (!(instance instanceof Node))
                throw new Error("Class is not a subclass of 'Node'")
            applyNextProps(instance, props)

            return instance
        },

        createTextInstance(_text) {
            throw new Error("createTextInstance not supported")
        },

        appendInitialChild(parent, child) {
            parent.add_child(child)
        },

        appendChild(parent, child) {
            parent.add_child(child)
        },

        appendChildToContainer(container, child) {
            container.add_child(child)
        },

        removeChild(parent, child) {
            parent.remove_child(child)
        },

        removeChildFromContainer(container, child) {
            container.remove_child(child)
        },

        insertBefore(_parent, child, before) {
            before.add_sibling(child)
        },

        finalizeInitialChildren() {
            return false
        },

        prepareForCommit() { return null },
        resetAfterCommit() {},

        getRootHostContext() {
            return {}
        },

        getChildHostContext(parentHostContext, _type, _rootContainer) {
            return parentHostContext
        },

        shouldSetTextContent() {
            return false
        },

        commitUpdate(instance, _type, _prevProps, nextProps, _internalHandle) {
            applyNextProps(instance, nextProps)
        },

        commitTextUpdate(_textInstance, _oldText, _newText) {
            throw new Error("commitTextUpdate not supported")
        },

        getPublicInstance(instance) {
            return instance
        },

        resolveUpdatePriority() {
            return DefaultEventPriority
        },

        resolveEventTimeStamp() {
            return ++eventTime
        },

        trackSchedulerEvent() {},

        resolveEventType() {
            return null
        },

        getCurrentUpdatePriority() {
            return currentPriority
        },

        setCurrentUpdatePriority(newPriority) {
            currentPriority = newPriority
        },

        clearContainer(container) {
            for (const child of container.get_children()) {
                container.remove_child(child)
            }
        },

        scheduleTimeout: setTimeout,
        cancelTimeout: clearTimeout,
        noTimeout: -1,

        isPrimaryRenderer: true,
        supportsPersistence: false,

        preparePortalMount(_containerInfo) {
            throw new Error("Function not implemented.")
        },
        getInstanceFromNode(_node) {
            throw new Error("Function not implemented.")
        },
        beforeActiveInstanceBlur() {
            throw new Error("Function not implemented.")
        },
        afterActiveInstanceBlur() {
            throw new Error("Function not implemented.")
        },
        prepareScopeUpdate(_scopeInstance, _instance) {
            throw new Error("Function not implemented.")
        },
        getInstanceFromScope(_scopeInstance) {
            throw new Error("Function not implemented.")
        },
        detachDeletedInstance(_node) {
            throw new Error("Function not implemented.")
        },

        supportsHydration: false,
        NotPendingTransition: undefined,
        HostTransitionContext: undefined as any,

        resetFormInstance(_form) {
            throw new Error("Function not implemented.")
        },
        requestPostPaintCallback(_callback: (time: number) => void) {
            throw new Error("Function not implemented.")
        },
        shouldAttemptEagerTransition: (): boolean => {
            throw new Error("Function not implemented.")
        },
        maySuspendCommit(_type, _props) {
            throw new Error("Function not implemented.")
        },
        preloadInstance(_type, _props) {
            throw new Error("Function not implemented.")
        },
        startSuspendingCommit() {
            throw new Error("Function not implemented.")
        },
        suspendInstance(_type, _props) {
            throw new Error("Function not implemented.")
        },
        waitForCommitToBeReady() {
            throw new Error("Function not implemented.")
        },
    })
}

const applyNextProps = (instance: Node<NodePathMap>, nextProps: Record<string, unknown>) => {
    Object.keys(nextProps).forEach(name => {
        (instance as any)[name] = nextProps[name]
    })

    if (hasProperty(nextProps, "name")) {
        if (typeof nextProps.name !== "string")
            throw new Error("Prop 'name' should be a string")
        instance.set_name(nextProps.name)
    }
}
