import { EventEmitter } from 'eventemitter3'

export class Primitive {
  name?: string
  function: Function
  functionIndex: number
  items: Array<Primitive>
  type: 'parallel' | 'sequence'
  _functionTreePrimitive: boolean
  outputs?: { [name: string]: Primitive }
}

export type TFunctionTreeExecutable =
  | Primitive
  | Function
  | TPath
  | TSequenceArray

type TPath = {
  [key: string]: TSequenceArray | Function
}

type TSequenceArray = Array<Function | TPath>

interface IPayload {
  [key: string]: any
  [key: number]: any
}

export interface IPath {
  path: string
  payload: IPayload
}

type TContextProviders = {
  [key: string]: Provider
}

export class FunctionTree extends EventEmitter {
  constructor(contextProviders?: TContextProviders)
  run(sequence: TSequenceArray, payload?: IPayload): Promise<any>
}

export interface IBaseContext {
  path: any
  resolve: IResolve
}

export interface IContext<T = {}> extends IBaseContext {
  props: T
}

type TTagFactory<T> = (
  path: TemplateStringsArray | string[],
  ...values: any[]
) => Tag<T>

export function createTemplateTag<T = any>(
  tagName: string,
  getValue: (path: string, context: IBaseContext) => T
): TTagFactory<T>

export function extractValueWithPath<T = any>(obj: any, path: string): T

export function resolveObject<T = any>(obj: any): ResolveValue<T>

type ProviderCb = (context: IContext) => { [key: string]: Function }

export class Provider<IContext = {}> {
  constructor(
    definition:
      | {
          [key: string]: (this: { context: IContext }, ...args: any[]) => void
        }
      | ProviderCb
  )
}

export class Path implements IPath {
  path: string
  payload: IPayload
  constructor(path: string, payload: IPayload)
  toJS(): IPath
}

export function sequence(items: Function | TSequenceArray): () => void
export function sequence<Props>(
  items: Function | TSequenceArray
): (props: Props) => void
export function sequence(
  name: string,
  items: Function | TSequenceArray
): () => void
export function sequence<Props>(
  name: string,
  items: Function | TSequenceArray
): (props: Props) => void
export function parallel(items: Array<Function | TSequenceArray>): () => void
export function parallel<Props>(
  items: Array<Function | TSequenceArray>
): (props: Props) => void
export function parallel(
  name: string,
  items: Array<Function | TSequenceArray>
): () => void
export function parallel<Props>(
  name: string,
  items: Array<Function | TSequenceArray>
): (props: Props) => void

export class ResolveValue<T = any> {}

export class Tag<T = any> extends ResolveValue<T> {
  constructor()
  getPath(getters: any): string
  getValue(getters: any): T
  type: string
}

export interface IResolve {
  isTag(arg: any, ...types: string[]): arg is Tag
  isResolveValue(arg: any): arg is ResolveValue
  path(tag: Tag): string
  value<T>(value: Tag<T>, overrideContext?: any): T
  value<T>(value: ResolveValue<T>, overrideContext?: any): T
  value<T>(value: Tag<T> | T, overrideContext?: any): T
  value<T = any>(value: ResolveValue<T> | T, overrideContext?: any): T
}
