import { NitroModules } from 'react-native-nitro-modules'

import type { UnistylesShadowRegistry as UnistylesShadowRegistrySpec } from './ShadowRegistry.nitro'
import type { ShadowNode, Unistyle, ViewHandle } from './types'

interface ShadowRegistry extends UnistylesShadowRegistrySpec {
    // Babel API
    add(handle?: ViewHandle, styles?: Array<Unistyle>): void
    remove(handle?: ViewHandle): void
    // JSI
    link(node: ShadowNode, styles?: Array<Unistyle>): void
    unlink(node: ShadowNode): void
    suspend(node: ShadowNode): void
    flush(): void
    setScopedTheme(themeName?: string): void
    getScopedTheme(): string | undefined
}

const HybridShadowRegistry = NitroModules.createHybridObject<ShadowRegistry>('UnistylesShadowRegistry')

const SUSPENSE_TAG = 13

const isInsideSuspendedBoundary = (fiber: any): boolean => {
    let current = fiber?.return

    while (current) {
        if (current.tag === SUSPENSE_TAG && current.memoizedState !== null) {
            return true
        }

        current = current.return
    }

    return false
}

const findFiberForHandle = (handle: ViewHandle) => {
    return (
        handle?.__internalInstanceHandle ??
        handle?.getScrollResponder?.()?.getNativeScrollRef?.()?.__internalInstanceHandle ??
        handle?.getNativeScrollRef?.()?.__internalInstanceHandle ??
        handle?._viewRef?.__internalInstanceHandle ??
        handle?.viewRef?.current?.__internalInstanceHandle ??
        handle?._nativeRef?.__internalInstanceHandle
    )
}

const findShadowNodeForHandle = (handle: ViewHandle) => {
    const node =
        handle?.__internalInstanceHandle?.stateNode?.node ??
        handle?.getScrollResponder?.()?.getNativeScrollRef?.()?.__internalInstanceHandle?.stateNode?.node ??
        handle?.getNativeScrollRef?.()?.__internalInstanceHandle?.stateNode?.node ??
        handle?._viewRef?.__internalInstanceHandle?.stateNode?.node ??
        handle?.viewRef?.current?.__internalInstanceHandle?.stateNode?.node ??
        handle?._nativeRef?.__internalInstanceHandle?.stateNode?.node

    // @ts-ignore we don't know the type of handle
    if (!node && handle?.props?.horizontal && handle?.constructor?.name === 'FlatList') {
        throw new Error(
            'Unistyles: detected an unsupported FlatList with the horizontal prop. This will cause crashes on Android due to a bug in React Native core. Read more: https://github.com/facebook/react-native/issues/51601',
        )
    }

    return node
}

HybridShadowRegistry.add = (handle, styles) => {
    // virtualized nodes can be null
    if (!handle || !styles) {
        return
    }

    const stylesArray = Array.isArray(styles) ? styles.flat() : [styles]

    // filter styles that are undefined or with no keys
    const filteredStyles = stylesArray
        .filter((style) => style && Object.keys(style).length > 0)
        .flat()
        .filter(Boolean)

    if (filteredStyles.length > 0) {
        const node = findShadowNodeForHandle(handle)

        if (!node) {
            throw new Error(
                `Unistyles: Could not find shadow node for one of your components of type ${handle?.constructor?.name ?? 'unknown'}`,
            )
        }

        HybridShadowRegistry.link(node, filteredStyles)
    }
}

HybridShadowRegistry.remove = (handle) => {
    if (!handle) {
        return
    }

    const maybeNode = findShadowNodeForHandle(handle)

    if (maybeNode) {
        const fiber = findFiberForHandle(handle)

        if (fiber && isInsideSuspendedBoundary(fiber)) {
            HybridShadowRegistry.suspend(maybeNode)
        } else {
            HybridShadowRegistry.unlink(maybeNode)
        }
    }
}

type PrivateMethods = 'add' | 'remove' | 'link' | 'unlink' | 'suspend'

export const UnistylesShadowRegistry = HybridShadowRegistry as Omit<ShadowRegistry, PrivateMethods>
