import toast from 'react-hot-toast'
import { assign, fromPromise, setup } from 'xstate'

import type { Operation } from '@rust/kcl-lib/bindings/Operation'

import {
  deleteSelectionPromise,
  deletionErrorMessage,
} from '@src/lang/modifyAst/deleteSelection'
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
import type { Artifact } from '@src/lang/std/artifactGraph'
import { getArtifactFromRange } from '@src/lang/std/artifactGraph'
import type { SourceRange } from '@src/lang/wasm'
import type { EnterEditFlowProps } from '@src/lib/operations'
import {
  enterAppearanceFlow,
  enterCloneFlow,
  enterEditFlow,
  enterTranslateFlow,
  enterRotateFlow,
} from '@src/lib/operations'
import { kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
import { commandBarActor } from '@src/lib/singletons'

type FeatureTreeEvent =
  | {
      type: 'goToKclSource'
      data: { targetSourceRange: SourceRange }
    }
  | {
      type: 'selectOperation'
      data: { targetSourceRange: SourceRange }
    }
  | {
      type: 'deleteOperation'
      data: { targetSourceRange: SourceRange }
    }
  | {
      type: 'enterEditFlow'
      data: { targetSourceRange: SourceRange; currentOperation: Operation }
    }
  | {
      type: 'enterAppearanceFlow'
      data: { targetSourceRange: SourceRange; currentOperation: Operation }
    }
  | {
      type: 'enterTranslateFlow'
      data: { targetSourceRange: SourceRange; currentOperation: Operation }
    }
  | {
      type: 'enterRotateFlow'
      data: { targetSourceRange: SourceRange; currentOperation: Operation }
    }
  | {
      type: 'enterCloneFlow'
      data: { targetSourceRange: SourceRange; currentOperation: Operation }
    }
  | { type: 'goToError' }
  | { type: 'codePaneOpened' }
  | { type: 'selected' }
  | { type: 'done' }
  | { type: 'xstate.error.actor.prepareEditCommand'; error: Error }
  | { type: 'xstate.error.actor.prepareDeleteCommand'; error: Error }

type FeatureTreeContext = {
  targetSourceRange?: SourceRange
  currentOperation?: Operation
}

export const featureTreeMachineDefaultContext: FeatureTreeContext = {}

export const featureTreeMachine = setup({
  types: {
    input: {} as FeatureTreeContext,
    context: {} as FeatureTreeContext,
    events: {} as FeatureTreeEvent,
  },
  guards: {
    codePaneIsOpen: () => false,
  },
  actors: {
    prepareEditCommand: fromPromise(
      ({
        input,
      }: {
        input: EnterEditFlowProps & {
          commandBarSend: (typeof commandBarActor)['send']
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { commandBarSend, ...editFlowProps } = input
          enterEditFlow(editFlowProps)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              input.commandBarSend(result)
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
    prepareAppearanceCommand: fromPromise(
      ({
        input,
      }: {
        input: EnterEditFlowProps & {
          commandBarSend: (typeof commandBarActor)['send']
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { commandBarSend, ...editFlowProps } = input
          enterAppearanceFlow(editFlowProps)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              input.commandBarSend(result)
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
    prepareTranslateCommand: fromPromise(
      ({
        input,
      }: {
        input: EnterEditFlowProps & {
          commandBarSend: (typeof commandBarActor)['send']
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { commandBarSend, ...editFlowProps } = input
          enterTranslateFlow(editFlowProps)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              input.commandBarSend(result)
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
    prepareRotateCommand: fromPromise(
      ({
        input,
      }: {
        input: EnterEditFlowProps & {
          commandBarSend: (typeof commandBarActor)['send']
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { commandBarSend, ...editFlowProps } = input
          enterRotateFlow(editFlowProps)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              input.commandBarSend(result)
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
    prepareCloneCommand: fromPromise(
      ({
        input,
      }: {
        input: EnterEditFlowProps & {
          commandBarSend: (typeof commandBarActor)['send']
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { commandBarSend, ...editFlowProps } = input
          enterCloneFlow(editFlowProps)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              input.commandBarSend(result)
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
    sendDeleteCommand: fromPromise(
      ({
        input,
      }: {
        input: {
          artifact: Artifact | undefined
          targetSourceRange: SourceRange | undefined
        }
      }) => {
        return new Promise((resolve, reject) => {
          const { targetSourceRange, artifact } = input
          if (!targetSourceRange) {
            reject(new Error(deletionErrorMessage))
            return
          }

          const pathToNode = getNodePathFromSourceRange(
            kclManager.ast,
            targetSourceRange
          )
          const selection = {
            codeRef: {
              range: targetSourceRange,
              pathToNode,
            },
            artifact,
          }
          deleteSelectionPromise(selection)
            .then((result) => {
              if (err(result)) {
                reject(result)
                return
              }
              resolve(result)
            })
            .catch(reject)
        })
      }
    ),
  },
  actions: {
    saveTargetSourceRange: assign({
      targetSourceRange: ({ event }) =>
        'data' in event && !err(event.data)
          ? event.data.targetSourceRange
          : undefined,
    }),
    saveCurrentOperation: assign({
      currentOperation: ({ event }) =>
        'data' in event && 'currentOperation' in event.data
          ? event.data.currentOperation
          : undefined,
    }),
    clearContext: assign({
      targetSourceRange: undefined,
    }),
    sendSelectionEvent: () => {},
    openCodePane: () => {},
    scrollToError: () => {},
  },
}).createMachine({
  /** @xstate-layout N4IgpgJg5mDOIC5QDMwEMAuBXATmAKnmAHQCWEANmAMRQD2+dA0gMYUDKduLYA2gAwBdRKAAOdWKQyk6AOxEgAHogCMAFhXF+27QDYArAGZ9AJhWGAHIYA0IAJ6q1ATkPELFkxf1qL-NQHYLNX0AXxDbVExcAiIyShpYMCoWDAB5UTAcTBlZAWEkEHFJaTkFZQQVJ00dPSNTcytbBwRnXWJ-fR0Op3cXJzUwiPRsPEIwEnIqajBZDEyAUQgpADEKOgB3PIUiqRyy1V1qmoNjM0sbe1V1TSd-XV1+XUM-EyeB8JBIkZjxuKmIJJgObpTLZORbAo7EryArlFQWI46E71c5NRD+fxqdqdfivDQmfx+QyDT7DaJjCbxWgMOjzHA4Og4CFiCS7UqwxBmRG1U4NC7NFSBYgmHH8O5VfgeR4kr7k2L0UiyKCMVgcLg4HjERLJaRK6jasApSDMwqs6H7Cr8IzEfQqby6NS4-ROK1ONEVfT6Npefr8FTmZyGXQysmjeV0RXK5hsTjcEgQOQ0E1QvYciqGbmPOpnCy6d0qe5OYjGFyBEVVExOENRMO-BVKlUx9WaugZWSRgDCdABAAU0LIaCxu2A+wOQQOIMmzanQOVDiZiA7XfwXK8MxZ857vc7Hf7DIHgx9ZbWSAaUpGtYDz3qz3NJ0JttP2bPELoLEWESK1CYTK6TBn3U9It-FeT1DCcSs-FMatvgpS8dQvBMB2oKdihnJRVH8TNkRzPNLgQJwnH0YhBScAxBQsEC7mJI9Qx+EgZjmHBI0WFY1nWeDDV1KB9SvO9ULZGEXwQIwF0MTwVDxPEN3wyTnkXL1nl0SsnmMUJaJrejiEYzIWKWDBVg2YgkKTB9ISfISMI9bDswaPCBUMDpF3En13CscSqw02DYh05ilVYgz2OIUQ8FENA8ACrsAFsov7CBqBMshZAANzoABrEhjy03y9LYoyQrAMKIv06LYtkCAEEVVKWDBXIhAE800wg1wvRFZ1XjI55-HzTwLBIlzCStbxKxooZNLgnL-P0wyOIKoqwEiugYri6Z6UZYKKEwZBGSi4gsom2ZdKmvLZtC8KFpKpayoqqq6Bq6E8ga9Dymam1lKAjqnjFfNKmI0j-EMSwfwxMUYLlX4ASobiQSyaFOOvHjb2NMyWTQ58rM9MSJO0NQHXcNR+UQLx+GFfgMw0XHcatFQwZPYzAWhjJYZyYzExQlHTTRyzyl-GzeVRfDHL65xCNXf0TGCfdaa0yGgUjGHavpqHI3YPicgSxMktSjK9rouDZcZ0E4YNlW1bkSqUru2rHo5lN0Ze79SeeXxnluKp9HdF1-EXfwyMOIiCZAw8xu8iGGflpnFZNpVVYQuRVoZHANq2nbdfG2Jo6gBXjfDmOzdkC3qut+rbYsi1+gXX9nbJldfbtd0ESxIP9AxB1nCtd4Q-Bkh6yjOlE+IVsZk7YdR0HUf+zAcdkfyVHBItZ02lFN9yy6kx3TMPrRXMAlAc6dSu7p3vGH79aTPZ2fOfntMuS0Y5bIF5pAczQHAn3Aswg+WRh3gAp9qIR8XMLQAFp7KIGARLaWcFJhgEAdfYS358xkXaFRDMmJ-TKXuFA8MkZGxqjjHAxqwl9yuAeC4ZS-QAZmAdPmNQuNiw7kopUbQv1sF1gjA2aM+CNSnjVkqQhz0iZWmLJJCmLdTCNFkkYVwPoCaVB6PoBEwdSTp3YbgrhsYeGswHAI+2VxfZaGUnvDEPRvpSMBm4HcGZCJeCUWwnuHCoyqk0S2NsI9eyT10dzRA4FNBkKDJWAI4kCxqE3BY2R1iFF2K8t3eG3EvEWnAqQlclh3ySiqADbq+E6FFh-AYZSK4XAaH8PYuJF5byRgSWmOhfiUm9HSYKRygEgztFAooqwKgVzQRiXTCpSptGwPMkAtMCJiIlg0OBZ0HhEH4SMG0Zu7SMxdJMKUyaUAAozSqcQp4i46lpPfI0rJAoyZYl0L7B0BZeYWFWYdPy6zppBT6VALZVkNC1JcPUg5mSeoZhIopR4Kk94HxUaHBitzcqBSMiZF5cICze03i7PwZFtDr1kj+LEdoKEQTuD4EpPTsrguOpC06hVzqLWWuVGFr4W5uExDY+EZMEQqB6mKBSykvD-jOZ5Q+Mtc5Z0jkQq+gryjnF2eQwJVCQnugMFiPJnpCTqCsN+Upmds4syeVSlovz-EUKCdQ0J+FibtDJv6T0NcVydxBbE1VAqWbQqGfAqy-ogjEBFkwlcdpBQGuaEEVwhIMyCnMISV4NN8X6z5WquQSs5Z5zjpZO23iEBnO9pRZwIFN6BG8A3AkTtJJ+CtK6S1-81GcNPjgTV4lva40ou4YwkohretUJKO+OggjsucKGnlcFj60jWknIe7YlRdg8Toh1wqfFOWrYEKwnRfCekbRUIiNoai2n6A8EU1yw04NLX2gZmrAhFkDQcl0v0ghIOIqKama6nSbrCEAA */
  id: 'featureTree',
  description: 'Workflows for interacting with the feature tree pane',
  context: ({ input }) => input,
  states: {
    idle: {
      on: {
        goToKclSource: {
          target: 'goingToKclSource',
          actions: 'saveTargetSourceRange',
        },

        selectOperation: {
          target: 'selecting',
          actions: 'saveTargetSourceRange',
        },

        enterEditFlow: {
          target: 'enteringEditFlow',
          actions: ['saveTargetSourceRange', 'saveCurrentOperation'],
        },

        enterAppearanceFlow: {
          target: 'enteringAppearanceFlow',
          actions: ['saveTargetSourceRange', 'saveCurrentOperation'],
        },

        enterTranslateFlow: {
          target: 'enteringTranslateFlow',
          actions: ['saveTargetSourceRange', 'saveCurrentOperation'],
        },

        enterRotateFlow: {
          target: 'enteringRotateFlow',
          actions: ['saveTargetSourceRange', 'saveCurrentOperation'],
        },

        enterCloneFlow: {
          target: 'enteringCloneFlow',
          actions: ['saveTargetSourceRange', 'saveCurrentOperation'],
        },

        deleteOperation: {
          target: 'deletingOperation',
          actions: ['saveTargetSourceRange'],
        },

        goToError: 'goingToError',
      },
    },

    goingToKclSource: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'done',
            },
          },

          entry: ['sendSelectionEvent'],
        },

        done: {
          entry: ['clearContext'],
          always: '#featureTree.idle',
        },

        openingCodePane: {
          on: {
            codePaneOpened: 'selecting',
          },

          entry: 'openCodePane',
        },
      },

      initial: 'openingCodePane',
    },

    selecting: {
      states: {
        selecting: {
          on: {
            selected: 'done',
          },

          entry: 'sendSelectionEvent',
        },

        done: {
          always: '#featureTree.idle',
          entry: 'clearContext',
        },
      },

      initial: 'selecting',
    },

    enteringEditFlow: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'prepareEditCommand',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        prepareEditCommand: {
          invoke: {
            src: 'prepareEditCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                // currentOperation is guaranteed to be defined here
                operation: context.currentOperation!,
                artifact,
                commandBarSend: commandBarActor.send,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    enteringAppearanceFlow: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'prepareAppearanceCommand',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        prepareAppearanceCommand: {
          invoke: {
            src: 'prepareAppearanceCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                // currentOperation is guaranteed to be defined here
                operation: context.currentOperation!,
                artifact,
                commandBarSend: commandBarActor.send,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    enteringTranslateFlow: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'prepareTranslateCommand',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        prepareTranslateCommand: {
          invoke: {
            src: 'prepareTranslateCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                // currentOperation is guaranteed to be defined here
                operation: context.currentOperation!,
                artifact,
                commandBarSend: commandBarActor.send,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    enteringRotateFlow: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'prepareRotateCommand',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        prepareRotateCommand: {
          invoke: {
            src: 'prepareRotateCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                // currentOperation is guaranteed to be defined here
                operation: context.currentOperation!,
                artifact,
                commandBarSend: commandBarActor.send,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    enteringCloneFlow: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'prepareCloneCommand',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        prepareCloneCommand: {
          invoke: {
            src: 'prepareCloneCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                // currentOperation is guaranteed to be defined here
                operation: context.currentOperation!,
                artifact,
                commandBarSend: commandBarActor.send,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    deletingOperation: {
      states: {
        selecting: {
          on: {
            selected: {
              target: 'deletingSelection',
              reenter: true,
            },
          },
        },

        done: {
          always: '#featureTree.idle',
        },

        deletingSelection: {
          invoke: {
            src: 'sendDeleteCommand',
            input: ({ context }) => {
              const artifact = context.targetSourceRange
                ? (getArtifactFromRange(
                    context.targetSourceRange,
                    kclManager.artifactGraph
                  ) ?? undefined)
                : undefined
              return {
                artifact,
                targetSourceRange: context.targetSourceRange,
              }
            },
            onDone: {
              target: 'done',
              reenter: true,
            },
            onError: {
              target: 'done',
              reenter: true,
              actions: ({ event }) => {
                if ('error' in event && err(event.error)) {
                  toast.error(event.error.message)
                }
              },
            },
          },
        },
      },

      initial: 'selecting',
      entry: 'sendSelectionEvent',
      exit: ['clearContext'],
    },

    goingToError: {
      states: {
        openingCodePane: {
          entry: 'openCodePane',

          on: {
            codePaneOpened: 'done',
          },
        },

        done: {
          entry: 'scrollToError',

          always: '#featureTree.idle',
        },
      },

      initial: 'openingCodePane',
    },
  },

  initial: 'idle',
})
