import React, {
  memo,
  useCallback,
  useDebugValue,
  useEffect,
  useId,
  type FC,
  type PropsWithChildren,
  type ReactNode,
} from 'react';
import { useCompareEffect } from '@wener/reaction';
import { createStore } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { shallow } from 'zustand/shallow';
import { createStoreContext } from '../../zustand';

export interface SlotProps<
  S extends Record<string, string> = {},
  N extends string = keyof S & string,
  P extends string = S[N],
> {
  name: N;
  placement?: P;
  order?: number;
  children: ReactNode;
}
export interface SlotPlaceholderProps<
  S extends Record<string, string> = {},
  N extends string = keyof S & string,
  P extends string = S[N],
> {
  name: N;
  // aux
  placement?: P;
  // placeholder for missing slot content
  placeholder?: ReactNode;
}

export interface SlotData {
  id: string;
  name: string;
  placement: string;
  order: number;
  children: ReactNode;
}

interface SlotStore {
  slots: Record<string, SlotData[]>;
}

const createSlotStore = () => createStore<SlotStore>()(immer(() => ({ slots: {} })));

export function createSlotContext<SlotTypes extends Record<string, string> = {}>() {
  const { Provider, useStore, useStoreApi } = createStoreContext<ReturnType<typeof createSlotStore>>();
  const SlotProvider: FC<PropsWithChildren<{ name?: string }>> = ({ children }) => {
    return <Provider createStore={createSlotStore}>{children}</Provider>;
  };

  const Slot = memo<SlotProps<SlotTypes>>(({ name, placement = 'default', order = 0, children }) => {
    const api = useStoreApi();
    const slotId = useId();
    placement ||= 'default';
    order ||= 0;
    const slotPlace = `${name}/${placement}`;
    useCompareEffect(() => {
      // wrap children with key
      api.setState((s) => {
        const slots = (s.slots[slotPlace] ||= []);
        const slot = slots.find((v) => v.id === slotId);
        if (slot) {
          Object.assign(slot, {
            name,
            placement,
            order,
            children,
          });
        } else {
          slots.push({
            id: slotId,
            name,
            placement,
            order,
            children,
          });
        }
        slots.sort((a, b) => b.order - a.order);
      });
    }, [slotPlace, order, children]);
    useEffect(() => {
      return () => {
        api.setState((s) => {
          s.slots[slotPlace] = s.slots[slotPlace]?.filter((v) => v.id !== slotId);
        });
      };
    }, []);
    useDebugValue(`Slot ${slotPlace}@{${slotId}`);
    return null;
  });
  Slot.displayName = 'Slot';

  function useSlot({ name, placement }: { name: string; placement?: string }) {
    const slotPlace = `${name}/${placement || 'default'}`;
    return useStore(
      useCallback((s) => s.slots[slotPlace] || [], [slotPlace]),
      shallow,
    );
  }

  const SlotPlaceholder = memo<SlotPlaceholderProps<SlotTypes>>(({ name, placement, placeholder }) => {
    const slotPlace = `${name}/${placement || 'default'}`;
    const slot = useStore(
      useCallback((s) => s.slots[slotPlace] || [], [slotPlace]),
      shallow,
    );
    useDebugValue(`SlotPlaceholder ${name}${placement ? `@${placement}` : ''}`);
    const children = slot.length
      ? slot.map((v) => {
          return v.children;
        })
      : placeholder;
    return <>{children}</>;
  });
  SlotPlaceholder.displayName = 'SlotPlaceholder';
  return { Slot, useSlot, SlotPlaceholder, SlotProvider };
}

export const { Slot, SlotPlaceholder, SlotProvider, useSlot } = createSlotContext();
