"use client";
import React, { useState, useRef, useMemo, useCallback, PropsWithChildren } from "react";
import {
    MediaPickerContext,
    MediaPickerContextTepe,
    MediaPickerPickParams,
    MediaPickerEventType,
    MediaPickerEventHandler,
    MediaPickerEvents,
    Media
} from "@/components/ui/media-picker/media-picker.context";
import { MediaPicker } from "@/components/ui/media-picker/media-picker";

type MediaPickerProviderProps = PropsWithChildren;

export const MediaPickerProvider: React.FC<MediaPickerProviderProps> = (props) => {
    const {
        children
    } = props;

    const [isOpen, setOpen] = useState(false),
          [attached, setAttached] = useState<string[]>([]),
          [params, setParams] = useState<MediaPickerPickParams>();
    const listeners = useRef<Map<MediaPickerEventType, Set<MediaPickerEventHandler<MediaPickerEventType>>>>(new Map());
    const resolveRef = useRef<(ids: string[]) => void>(null);
    const rejectRef = useRef<(err?: Error) => void>(null);

    const handleEmit = useCallback(<T extends MediaPickerEventType>(event: T, payload: MediaPickerEvents[T]) => {
        listeners.current.get(event)?.forEach(handler => handler(payload));
    }, []);

    const handlePick = useCallback(async (params: MediaPickerPickParams) => {
        setOpen(true);
        setParams(params);

        return new Promise<string[]>((resolve, reject) => {
            resolveRef.current = resolve;
            rejectRef.current = reject;
        });
    }, []);

    const handleSelect = useCallback((ids: string[]) => {
        if(resolveRef.current) {
            resolveRef.current(ids);

            resolveRef.current = null;
            rejectRef.current = null;
        }

        setOpen(false);
    }, []);

    const handleOpenChange = useCallback((open: boolean) => {
        if(!open && rejectRef.current) {
            rejectRef.current(new Error("Media picker cancelled"));

            resolveRef.current = null;
            rejectRef.current = null;
        }

        setOpen(open);
    }, []);

    const handleCreateMany = useCallback((medias: Media[]) => {
        handleEmit("createMany", { medias });
    }, [handleEmit]);

    const handleSetAttached = useCallback((ids: string[]) => {
        setAttached((attached) => {
            const attachedSet = new Set(attached);

            const oldIds = ids.filter((id) => attachedSet.has(id));
            const newIds = ids.filter(id => !attachedSet.has(id));

            if(oldIds.length === attached.length && newIds.length === 0) {
                return attached;
            }

            return [...ids];
        });
    }, []);

    const handleAttach = useCallback((ids: string[]) => {
        setAttached((attached) => {
            const attachedSet = new Set(attached);

            const newAttached = ids.filter(id => !attachedSet.has(id));

            if(newAttached.length === 0) {
                return attached;
            }

            const nextAttached = [
                ...attached,
                ...newAttached
            ];

            handleEmit("attach", { attached: nextAttached });

            return nextAttached;
        });
    }, [handleEmit]);

    const handleOn = useCallback(<T extends MediaPickerEventType>(event: T, handler: MediaPickerEventHandler<T>) => {
        if(!listeners.current.has(event)) {
            listeners.current.set(event, new Set());
        }

        listeners.current.get(event)?.add(handler as MediaPickerEventHandler<MediaPickerEventType>);
    }, []);

    const handleOff = useCallback(<T extends MediaPickerEventType>(event: T, handler: MediaPickerEventHandler<T>) => {
        if(!listeners.current.has(event)) {
            return;
        }

        listeners.current.get(event)?.delete(handler as MediaPickerEventHandler<MediaPickerEventType>);
    }, []);

    const context = useMemo((): MediaPickerContextTepe => {
        return {
            attached,
            pick: handlePick,
            setAttached: handleSetAttached,
            attach: handleAttach,
            on: handleOn,
            off: handleOff,
            emit: handleEmit
        };
    }, [attached, handlePick, handleSetAttached, handleAttach, handleOn, handleOff, handleEmit]);

    return (
        <MediaPickerContext value={context}>
            {children}

            <MediaPicker
              {...params}
              open={isOpen}
              attached={attached}
              onSelect={handleSelect}
              onCreate={handleCreateMany}
              onOpenChange={handleOpenChange} />
        </MediaPickerContext>
    );
};
