import {nanoid} from "nanoid"

export interface BottomSheetToolboxDevToolsAdapter {
    // biome-ignore lint/suspicious/noExplicitAny: not useful to type
    sendMessage(method: string, parameters: any): void
    // biome-ignore lint/suspicious/noExplicitAny: not useful to type
    addMessageListener(method: string, listener: (parameters: any) => void): () => void
}

export type BottomSheetToolboxDevToolsMessageFromRN =
    | {
          type: "upsert-sheet"
          payload: {
              sheetId: string
              currentPosition: number
              wrapperHeight: number | null
              snapPoints: number[] | null
              isReady: boolean
          }
      }
    | {
          type: "delete-sheet"
          payload: {
              sheetId: string
          }
      }
    | {
          type: "update-device-config"
          payload: {
              width: number
              height: number
              theme: "light" | "dark"
          }
      }

export type BottomSheetToolboxDevToolsMessageFromDevTools = {
    type: "did-connect"
}

class BottomSheetToolboxDevTools {
    private _adaptersWithListenerRemovers: {
        adapter: BottomSheetToolboxDevToolsAdapter
        listenerRemovers: Map<string, () => void>
    }[] = []
    private readonly _messageListeners = new Map<
        string,
        // biome-ignore lint/suspicious/noExplicitAny: not useful to type
        readonly [string, (parameters: any) => void]
    >()

    public addAdapter(adapter: BottomSheetToolboxDevToolsAdapter) {
        const adapterWithListenerRemovers = {
            adapter,
            listenerRemovers: new Map(),
        }
        this._adaptersWithListenerRemovers.push(adapterWithListenerRemovers)

        for (const [messageListenerId, listenerArgs] of this._messageListeners.entries()) {
            const [method, listener] = listenerArgs
            const removeListener = adapter.addMessageListener(method, listener)
            adapterWithListenerRemovers.listenerRemovers.set(messageListenerId, removeListener)
        }

        return () => {
            this._adaptersWithListenerRemovers = this._adaptersWithListenerRemovers.filter(
                (adapterWithListenerRemovers) => adapterWithListenerRemovers.adapter !== adapter
            )
        }
    }

    public sendMessage<Method extends BottomSheetToolboxDevToolsMessageFromRN["type"]>(
        method: Method,
        parameters: Extract<BottomSheetToolboxDevToolsMessageFromRN, {type: Method}> extends {
            payload: infer Payload
        }
            ? Payload
            : never
    ) {
        // Message sent before an adapter is added are lost.
        this._adaptersWithListenerRemovers.forEach((adapterWithListenerRemovers) => {
            adapterWithListenerRemovers.adapter.sendMessage(method, parameters)
        })
    }

    public addMessageListener<Method extends BottomSheetToolboxDevToolsMessageFromDevTools["type"]>(
        method: Method,
        listener: (
            parameters: Extract<
                BottomSheetToolboxDevToolsMessageFromDevTools,
                {type: Method}
            > extends {payload: infer Payload}
                ? Payload
                : never
        ) => void
    ) {
        const messageListenerId = nanoid()
        const listenerArgs = [method, listener] as const
        this._messageListeners.set(messageListenerId, listenerArgs)

        for (const adapterWithListenerRemovers of this._adaptersWithListenerRemovers) {
            const removeListener = adapterWithListenerRemovers.adapter.addMessageListener(
                method,
                listener
            )
            adapterWithListenerRemovers.listenerRemovers.set(messageListenerId, removeListener)
        }

        return () => {
            this._messageListeners.delete(messageListenerId)

            for (const adapterWithListenerRemovers of this._adaptersWithListenerRemovers) {
                const removeListener =
                    adapterWithListenerRemovers.listenerRemovers.get(messageListenerId)
                removeListener?.()
            }
        }
    }
}

/**
 * This is for internal use, so we can log to our separate devtools package.
 */
export const BOTTOM_SHEET_DEV_TOOLS = new BottomSheetToolboxDevTools()
