import Suspense, { Fallback } from ':islands/Suspens.tsx'
import { requestApiStream } from ':src/utils.ts'
import { Signal, useSignal } from '@preact/signals'
import type { JSX, Ref } from 'preact'
import { useEffect, useRef } from 'preact/hooks'

export type Builder<T> = (props: T) => JSX.Element

export type CardListProps<ApiResponse, RefType = null> = {
    apiRoute: string
    builder: Builder<ApiResponse>
    limit?: number
} | {
    apiRoute: string
    builder: Builder<ApiResponse>
    limit?: number
    useObserver?: boolean
    placeholder: ({ ref }: { ref: Ref<RefType> | undefined }) => JSX.Element
    fallback: Fallback
}

export default function CardList<ApiResponse, RefType = null>(
    { limit, builder, ...props }: CardListProps<ApiResponse, RefType>,
) {
    const list: Signal<JSX.Element[]> = useSignal<JSX.Element[]>([])
    const ac = new AbortController()
    const ref = useRef(null)

    const useObserver = 'useObserver' in props ? props.useObserver : false
    const placeholder = 'placeholder' in props ? props.placeholder : false
    const fallback = 'fallback' in props ? props.fallback : false

    useEffect(() => {
        if (ref.current && useObserver) {
            const observer = new IntersectionObserver(([entry]) => {
                if (entry.isIntersecting) {
                    fillList(list, builder, { limit, ac })
                    observer.disconnect()
                }
            }, {
                rootMargin: '300px',
            })
            observer.observe(ref.current)
        } else {
            fillList(list, builder, { limit, ac })
        }
    })

    if (limit && placeholder && fallback) {
        const placeholders = Array
            .from({ length: limit })
            .map((_, index) => (
                <Suspense
                    loader={placeholder({ ref: index === 0 ? ref : undefined })}
                    fallback={fallback}
                    signal={ac.signal}
                >
                    {updateFromList(list, index)}
                </Suspense>
            ))
        return <>{placeholders}</>
    }

    return <>{list}</>
}

function fillList<ApiResponse>(
    list: Signal<JSX.Element[]>,
    builder: Builder<ApiResponse>,
    { limit, ac }: { limit?: number; ac?: AbortController },
) {
    ;(async () => {
        const propsList = requestApiStream<void, ApiResponse>(
            'news/fetchAll',
            'GET',
        )

        for await (const props of propsList) {
            list.value = [
                ...list.value,
                builder(props),
            ]
            if (limit && list.value.length >= limit) break
        }
        ac?.abort()
    })()
}

function updateFromList(
    list: Signal<JSX.Element[]>,
    index: number,
): Promise<JSX.Element> {
    const { promise, resolve } = Promise.withResolvers<JSX.Element>()
    list.subscribe((value: JSX.Element[]) => {
        const selected = value.at(index)
        if (selected) {
            resolve(selected)
        }
    })

    return promise
}
