import {
  GraphAddUnitData,
  GraphCoverPinSetData,
  GraphExposePinSetData,
  GraphRemoveMergeData,
  GraphRemoveMergeDataData,
  GraphRemoveUnitData,
  GraphRemoveUnitPinDataData,
  GraphUnplugPinData,
} from '../../Class/Graph/interface'
import { Position } from '../../client/util/geometry'
import { keys } from '../../system/f/object/Keys/f'
import {
  Action,
  GraphMergeSpec,
  GraphMergesSpec,
  GraphPinSpec,
  GraphSubPinSpec,
  GraphUnitsSpec,
} from '../../types'
import { AllKeys } from '../../types/AllKeys'
import { Dict } from '../../types/Dict'
import { G } from '../../types/interface/G'
import { IO } from '../../types/IO'
import { IOOf } from '../../types/IOOf'
import { UnitBundleSpec } from '../../types/UnitBundleSpec'
import {
  APPEND_SUB_COMPONENT_CHILDREN,
  makeAppendSubComponentChildrenAction as makeAppendSubComponentChildren,
  makeRemoveSubComponentChildrenAction as makeRemoveSubComponentChildren,
  makeReorderSubComponentAction,
  REMOVE_SUB_COMPONENT_CHILDREN,
  REORDER_SUB_COMPONENT,
} from './C'

export const ADD_UNIT = 'addUnitSpec'
export const ADD_UNITS = 'addUnits'
export const REMOVE_UNIT = 'removeUnit'
export const REMOVE_UNITS = 'removeUnits'
export const ADD_MERGE = 'addMerge'
export const ADD_MERGES = 'addMerges'
export const ADD_PIN_TO_MERGE = 'addPinToMerge'
export const REMOVE_PIN_FROM_MERGE = 'removePinFromMerge'
export const REMOVE_MERGE = 'removeMerge'
export const REMOVE_MERGE_DATA = 'removeMergeData'
export const REMOVE_MERGES = 'removeMerges'
export const MERGE_MERGES = 'mergeMerges'
export const EXPOSE_PIN = 'exposePin'
export const EXPOSE_PIN_SET = 'exposePinSet'
export const COVER_PIN_SET = 'coverPinSet'
export const COVER_PIN = 'coverPin'
export const UNPLUG_PIN = 'unplugPin'
export const PLUG_PIN = 'plugPin'
export const SET_UNIT_PIN_DATA = 'setUnitPinData'
export const REMOVE_UNIT_PIN_DATA = 'removeUnitPinData'
export const SET_UNIT_PIN_CONSTANT = 'setUnitPinConstant'
export const SET_UNIT_PIN_IGNORED = 'setUnitPinIgnored'
export const SET_UNIT_ERR = 'setUnitErr'
export const SET_PIN_SET_NAME = 'setPinSetName'
export const SET_PIN_SET_FUNCTIONAL = 'setPinSetFunctional'
export const SET_PIN_SET_REF = 'setPinSetRef'
export const SET_METADATA = 'setMetadata'
export const SET_UNIT_METADATA = 'setUnitMetadata'
export const REMOVE_UNIT_MERGES = 'removeUnitMerges'
export const EXPAND_UNIT = 'expandUnit'
export const COLLAPSE_UNITS = 'collapseUnits'

export const wrapAddUnitAction = (data: GraphAddUnitData) => {
  return {
    type: ADD_UNIT,
    data,
  }
}

export const makeAddUnitAction = (
  unitId: string,
  unit: UnitBundleSpec,
  position?: Position | undefined,
  pinPosition?: IOOf<Dict<Position>> | undefined,
  layoutPositon?: Position | undefined,
  parentId?: string | null | undefined,
  merges?: GraphMergesSpec | undefined
) => {
  return wrapAddUnitAction({
    unitId,
    bundle: unit,
    position,
    pinPosition,
    layoutPositon,
    parentId,
  })
}

export const makeAddUnitsAction = (units: GraphUnitsSpec) => {
  return {
    type: ADD_UNITS,
    data: {
      units,
    },
  }
}

export const wrapMakeRemoveUnitAction = (data: GraphRemoveUnitData) => {
  return {
    type: REMOVE_UNIT,
    data,
  }
}

export const makeRemoveUnitAction = (
  unitId: string,
  bundle: UnitBundleSpec,
  position?: Position,
  pinPosition?: { input: Dict<Position>; output: Dict<Position> },
  layoutPositon?: Position,
  parentId?: string | null,
  merges?: GraphMergesSpec
) => {
  return wrapMakeRemoveUnitAction({
    unitId,
    bundle,
    position,
    pinPosition,
    layoutPositon,
    parentId,
    merges,
  })
}

export const makeRemoveUnitsAction = (ids: string[]) => {
  return {
    type: REMOVE_UNITS,
    data: { ids },
  }
}

export const exposePinAction = (
  type: IO,
  id: string,
  subPinId: string,
  subPin: GraphSubPinSpec
) => {
  return {
    type: EXPOSE_PIN,
    data: { type, id, subPinId, subPin },
  }
}

export const setPinSetNameAction = (
  type: IO,
  id: string,
  functional: boolean
) => {
  return {
    type: SET_PIN_SET_NAME,
    data: { type, id, functional },
  }
}

export const setPinSetFunctionalAction = (
  type: IO,
  id: string,
  functional: boolean
) => {
  return {
    type: SET_PIN_SET_FUNCTIONAL,
    data: { type, id, functional },
  }
}

export const wrapExposePinSetAction = (data: GraphExposePinSetData) => {
  return {
    type: EXPOSE_PIN_SET,
    data,
  }
}

export const makeExposePinSetAction = (
  type: IO,
  pinId: string,
  pinSpec: GraphPinSpec
) => {
  return wrapExposePinSetAction({ type, pinId, pinSpec })
}

export const wrapCoverPinSetAction = (data: GraphCoverPinSetData) => {
  return {
    type: COVER_PIN_SET,
    data,
  }
}

export const makeCoverPinSetAction = (
  type: IO,
  pinId: string,
  pinSpec: GraphPinSpec
) => {
  return wrapCoverPinSetAction({ type, pinId, pinSpec })
}

export const makePlugPinAction = (
  type: IO,
  id: string,
  subPinId: string,
  subPinSpec: GraphSubPinSpec
) => {
  return {
    type: PLUG_PIN,
    data: { type, id, subPinId, subPinSpec },
  }
}

export const wrapUnplugPinAction = (data: GraphUnplugPinData) => {
  return {
    type: UNPLUG_PIN,
    data,
  }
}

export const makeUnplugPinAction = (
  type: IO,
  pinId: string,
  subPinId: string,
  subPinSpec: GraphPinSpec
) => {
  return wrapUnplugPinAction({ type, pinId, subPinId, subPinSpec })
}

export const makeCoverPinAction = (
  type: IO,
  id: string,
  subPinId: string,
  subPinSpec: GraphSubPinSpec
) => {
  return {
    type: COVER_PIN,
    data: { type, id, subPinId, subPinSpec },
  }
}

export const makeSetUnitPinDataAction = (
  unitId: string,
  type: IO,
  pinId: string,
  data: any
) => {
  return {
    type: SET_UNIT_PIN_DATA,
    data: {
      unitId,
      type,
      pinId,
      data,
    },
  }
}

export const makeSetUnitPinConstantAction = (
  unitId: string,
  type: IO,
  pinId: string,
  constant: boolean
) => {
  return {
    type: SET_UNIT_PIN_CONSTANT,
    data: {
      unitId,
      type,
      pinId,
      constant,
    },
  }
}

export const makeSetUnitPinIgnoredAction = (
  unitId: string,
  type: IO,
  pinId: string,
  ignored: boolean
) => {
  return {
    type: SET_UNIT_PIN_IGNORED,
    data: {
      unitId,
      type,
      pinId,
      ignored,
    },
  }
}

export const wrapRemoveUnitPinDataAction = (
  data: GraphRemoveUnitPinDataData
) => {
  return {
    type: REMOVE_UNIT_PIN_DATA,
    data,
  }
}

export const makeRemoveUnitPinDataAction = (
  unitId: string,
  type: IO,
  pinId: string
) => {
  return wrapRemoveUnitPinDataAction({
    unitId,
    type,
    pinId,
  })
}

export const makeSetUnitMetadataAction = (
  id: string,
  path: string[],
  value: any
) => {
  return {
    type: SET_UNIT_METADATA,
    data: {
      id,
      path,
      value,
    },
  }
}

export const makeSetMetadataAction = (path: string[], value: any) => {
  return {
    type: SET_METADATA,
    data: {
      path,
      value,
    },
  }
}

export const makeAddMergeAction = (
  mergeId: string,
  merge: GraphMergeSpec,
  position?: Position | undefined
): Action => {
  return {
    type: ADD_MERGE,
    data: {
      id: mergeId,
      merge,
      position,
    },
  }
}

export const wrapRemoveMergeActionData = (data: GraphRemoveMergeData) => {
  return {
    type: REMOVE_MERGE,
    data,
  }
}

export const makeRemoveMergeAction = (
  id: string,
  merge: GraphMergeSpec,
  position?: Position | undefined
): Action => {
  return wrapRemoveMergeActionData({
    mergeId: id,
    mergeSpec: merge,
    position,
  })
}

export const wrapRemoveMergeDataAction = (data: GraphRemoveMergeDataData) => {
  return {
    type: REMOVE_MERGE_DATA,
    data,
  }
}

export const makeAddMergesAction = (merges: GraphMergesSpec): Action => {
  return {
    type: ADD_MERGES,
    data: {
      merges,
    },
  }
}

export const makeRemoveMergesAction = (ids: string[]): Action => {
  return {
    type: REMOVE_MERGES,
    data: {
      ids,
    },
  }
}

export const makeAddPinToMergeAction = (
  id: string,
  type: IO,
  unitId: string,
  pinId: string
): Action => {
  return {
    type: ADD_PIN_TO_MERGE,
    data: {
      id,
      unitId,
      type,
      pinId,
    },
  }
}

export const makeRemovePinFromMergeAction = (
  id: string,
  type: IO,
  unitId: string,
  pinId: string
): Action => {
  return {
    type: REMOVE_PIN_FROM_MERGE,
    data: {
      id,
      unitId,
      type,
      pinId,
    },
  }
}

export const makeMergeMergesAction = (a: string, b: string): Action => {
  return {
    type: MERGE_MERGES,
    data: {
      a,
      b,
    },
  }
}

export const makeRemoveUnitMergesAction = (id: string): Action => {
  return {
    type: REMOVE_UNIT_MERGES,
    data: {
      id,
    },
  }
}

export const reverseAction = ({ type, data }: Action): Action => {
  switch (type) {
    case ADD_UNIT:
      return makeRemoveUnitAction(
        data.unitId,
        data.bundle,
        data.position,
        data.pinPosition,
        data.layoutPosition,
        data.parentId,
        data.merges
      )
    case ADD_UNITS:
      return makeRemoveUnitsAction(keys(data.units))
    case REMOVE_UNIT:
      return makeAddUnitAction(
        data.unitId,
        data.bundle,
        data.position,
        data.pinPosition,
        data.layoutPosition,
        data.parentId,
        data.merges
      )
    case REMOVE_UNITS:
      return makeAddUnitsAction(
        data.ids.reduce((acc, id) => {
          acc[id] = data.units[id]
          return acc
        }, {})
      )
    case REMOVE_UNIT_MERGES:
      return makeAddMergesAction(data.merges)
    case ADD_MERGE:
      return makeRemoveMergeAction(data.id, data.merge, data.position)
    case ADD_MERGES:
      return makeRemoveMergesAction(keys(data.merges))
    case ADD_PIN_TO_MERGE:
      return makeRemovePinFromMergeAction(
        data.id,
        data.type,
        data.unitId,
        data.pinId
      )
    case REMOVE_MERGE:
      return makeAddMergeAction(data.id, data.merge, data.position)
    case REMOVE_PIN_FROM_MERGE:
      return makeAddPinToMergeAction(
        data.id,
        data.type,
        data.unitId,
        data.pinId
      )
    case MERGE_MERGES:
      return makeMergeMergesAction(data.a, data.b)
    case EXPOSE_PIN_SET:
      return makeCoverPinSetAction(data.type, data.pinId, data.pin)
    case COVER_PIN_SET:
      return makeExposePinSetAction(data.type, data.pinId, data.plug)
    case PLUG_PIN:
      return makeUnplugPinAction(
        data.type,
        data.pinId,
        data.subPinId,
        data.subPinSpec
      )
    case UNPLUG_PIN:
      return makePlugPinAction(
        data.type,
        data.pinId,
        data.subPinId,
        data.subPinSpec
      )
    case SET_UNIT_PIN_CONSTANT:
      return makeSetUnitPinConstantAction(
        data.unitId,
        data.type,
        data.pinId,
        !data.constant
      )
    case SET_UNIT_PIN_IGNORED:
      return makeSetUnitPinIgnoredAction(
        data.unitId,
        data.type,
        data.pinId,
        !data.ignored
      )
    case APPEND_SUB_COMPONENT_CHILDREN:
      return makeRemoveSubComponentChildren(data.id, data.children)
    case REMOVE_SUB_COMPONENT_CHILDREN:
      return makeAppendSubComponentChildren(
        data.id,
        data.slotName,
        data.children
      )
    case REORDER_SUB_COMPONENT:
      return makeReorderSubComponentAction(
        data.parentId,
        data.childId,
        data.to,
        data.from
      )
    default:
      throw new Error('Irreversible')
  }
}

export const processAction = (
  action: Action,
  method: Partial<AllKeys<G, Function>>
): void => {
  const { type, data } = action

  if (!method[type]) throw new Error(`No method for ${type}`)

  method[type](data)
}

export const processActions = (
  actions: Action[],
  method: Partial<AllKeys<G, Function>>
): void => {
  actions.forEach((action) => processAction(action, method))
}
