import { throwAppError } from '@learnitive/base-utils';
import type { LearnitiveAppCommand } from '@learnitive/commands';
import { commandKeyToContext } from '@learnitive/constants';
import {
  type CommandExposedServices,
  useCoreServices,
} from '@learnitive/context';

import type { InferType, Validator } from '@learnitive/mini-js-utils';
import type {
  Command,
  CommandHandler,
  CommandHandlerContext,
  CommandKey,
  Store,
} from '@learnitive/types';
import { useEffect } from 'react';

type CommandArgs<C extends Command> = C['args'] extends null
  ? null
  : {
      [K in keyof C['args']]: C['args'][K] extends Validator<unknown>
        ? InferType<C['args'][K]>
        : never;
    };

export function c<const T extends LearnitiveAppCommand['id']>(
  id: T,
  handler: (
    services: Pick<
      CommandExposedServices,
      Extract<LearnitiveAppCommand, { id: T }>['dependencies']['services'][number]
    >,
    args: CommandArgs<Extract<LearnitiveAppCommand, { id: T }>>,
    commandKey: CommandKey<NoInfer<T>>,
  ) => void | Promise<void>,
): {
  id: T;
  handler: CommandHandler;
} {
  return {
    id,
    handler: handler as CommandHandler,
  };
}

export function useC<T extends LearnitiveAppCommand['id']>(
  id: T,
  handler: (
    services: Pick<
      CommandExposedServices,
      Extract<LearnitiveAppCommand, { id: T }>['dependencies']['services'][number]
    >,
    args: CommandArgs<Extract<LearnitiveAppCommand, { id: T }>>,
    commandKey: CommandKey<NoInfer<T>>,
  ) => void | Promise<void>,
) {
  const coreServices = useCoreServices();

  useEffect(() => {
    const unregister = coreServices.commandRegistry.registerHandler({
      id,
      handler: handler as CommandHandler,
    });
    return unregister;
  }, [id, coreServices, handler]);
}

export type ChildDispatcher<TId extends LearnitiveAppCommand['id']> = (
  id: TId,
  args: CommandArgs<Extract<LearnitiveAppCommand, { id: TId }>>,
) => void;

type ChildCommandsIds<T extends string> = Extract<
  LearnitiveAppCommand,
  { id: T }
>['dependencies'] extends {
  commands?: Array<infer C>;
}
  ? C extends string
    ? C
    : never
  : never;

export function getCtx<T extends string>(
  key: CommandKey<T>,
): {
  store: Store;
  dispatch: <TInput extends ChildCommandsIds<T>>(
    id: TInput,
    args: CommandArgs<Extract<LearnitiveAppCommand, { id: TInput }>>,
  ) => void;
} {
  const result = commandKeyToContext.get(key);
  if (!result) {
    throwAppError('error::command:unregistered', 'Command not registered', {
      commandId: key.key,
    });
  }
  return {
    dispatch: result.context.dispatch,
    store: result.context.store,
  } satisfies CommandHandlerContext;
}
