import Godot from "godot"
import * as React from "react"


export interface Component<T extends Godot.Node<Godot.NodePathMap>>
extends React.FunctionComponent<Props<T>>, Prototype<T> {}

export type Props<T extends Godot.Node<Godot.NodePathMap>> = {
    readonly ref?: React.RefObject<T | null>
} & {
    // biome-ignore lint/complexity/noBannedTypes: using Function here is completely fine
    [K in keyof T as T[K] extends Function | Godot.Signal ? never : K]?: T[K]
} & {
    [K in keyof T as T[K] extends Godot.Signal ? K : never]?: T[K] extends Godot.Signal<infer F>
        ? ((this: T, ...args: Parameters<F>) => ReturnType<F>) | ((this: T, ...args: Parameters<F>) => Promise<ReturnType<F>>)
        : never
}

export interface Prototype<T extends Godot.Node<Godot.NodePathMap>> {
    useRef(): React.RefObject<T | null>
    useUnsafeRef(): React.RefObject<T>
}

export interface InstrinsicAttributes extends React.Attributes {
    readonly children?: React.ReactNode
    readonly name?: string
}

export const Prototype: Prototype<any> = {
    useRef() { return React.useRef(null) },
    useUnsafeRef() { return React.useRef(null) },
}

export const fromClass = <T extends Godot.Node<Godot.NodePathMap>>(
    class_: new (...args: any[]) => T
): Component<T> => Object.setPrototypeOf(
    Object.assign(
        (props: Props<T>) => React.createElement("node", { ...props, class: class_ }),
        { displayName: class_.name },
    ),
    Prototype,
)

export const fromSceneUnsafe = <T extends Godot.Node<Godot.NodePathMap> = Godot.Node<Godot.NodePathMap>>(
    path: string
): Component<T> => Object.setPrototypeOf(
    Object.assign(
        (props: Props<T>) => React.createElement("scene", { ...props, path }),
        { displayName: path },
    ),
    Prototype,
)

export declare namespace fromScene {
    export type SceneNames = {
        [K in keyof Godot.ResourceTypes]: Godot.ResourceTypes[K] extends Godot.PackedScene ? K : never
    }[keyof Godot.ResourceTypes]
}

export const fromScene = <A extends fromScene.SceneNames>(
    path: A
): Component<Godot.ResourceTypes[A] extends Godot.PackedScene<infer T> ? T : never> => fromSceneUnsafe(path)

export declare namespace useSignal {
    export type SignalNames<T extends Godot.Node<Godot.NodePathMap>> = {
        [K in keyof T & string]: T[K] extends Godot.Signal ? K : never
    }[keyof T & string]

    export type Function<T extends Godot.Node<Godot.NodePathMap>, N extends useSignal.SignalNames<T>> = (
        T[N] extends Godot.Signal<infer F>
            ? ((this: T, ...args: Parameters<F>) => ReturnType<F>) | ((this: T, ...args: Parameters<F>) => Promise<ReturnType<F>>)
            : never
    )
}

export const useSignal = <T extends Godot.Node<Godot.NodePathMap>, A extends useSignal.SignalNames<T>>(
    ref: React.RefObject<T | null>,
    name: A,
    f: useSignal.Function<T, A>,
// biome-ignore lint/correctness/useExhaustiveDependencies: "f" is non-reactive
): void => React.useEffect(() => {
    if (!ref.current) return
    const signal = ref.current[name] as Godot.Signal
    const callable = Godot.Callable.create(ref.current, f)
    signal.connect(callable)
    return () => { signal.disconnect(callable) }
}, [ref.current, name])

export declare namespace useSignalValues {
    export type SignalNames<T extends Godot.Node<Godot.NodePathMap>> = useSignal.SignalNames<T>

    export type SignalValues<T extends Godot.Node<Godot.NodePathMap>, N extends useSignal.SignalNames<T>> = (
        T[N] extends Godot.Signal<infer F>
            ? Parameters<F>
            : never
    )
}

export const useSignalValues = <T extends Godot.Node<Godot.NodePathMap>, A extends useSignal.SignalNames<T>>(
    ref: React.RefObject<T | null>,
    name: A,
    initialValue:
        | useSignalValues.SignalValues<NoInfer<T>, NoInfer<A>>
        | (() => useSignalValues.SignalValues<NoInfer<T>, NoInfer<A>>),
): useSignalValues.SignalValues<T, A> => {
    const [values, setValues] = React.useState(initialValue)
    useSignal(ref, name, ((...args: any) => setValues(args)) as any)
    return values
}
