import type { WorkflowHistoryEventMeta } from '../store/workflow/history-slice'
import { debounce } from 'es-toolkit/compat'
import {
  useCallback,
  useRef,
  useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import {
  useStoreApi,
} from 'reactflow'
import { useWorkflowHistoryStore } from '../workflow-history-store'

/**
 * All supported Events that create a new history state.
 * Current limitations:
 * - InputChange events in Node Panels do not trigger state changes.
 * - Resizing UI elements does not trigger state changes.
 */
export const WorkflowHistoryEvent = {
  NodeTitleChange: 'NodeTitleChange',
  NodeDescriptionChange: 'NodeDescriptionChange',
  NodeDragStop: 'NodeDragStop',
  NodeChange: 'NodeChange',
  NodeConnect: 'NodeConnect',
  NodePaste: 'NodePaste',
  NodeDelete: 'NodeDelete',
  EdgeDelete: 'EdgeDelete',
  EdgeDeleteByDeleteBranch: 'EdgeDeleteByDeleteBranch',
  EdgeSourceHandleChange: 'EdgeSourceHandleChange',
  NodeAdd: 'NodeAdd',
  NodeResize: 'NodeResize',
  NoteAdd: 'NoteAdd',
  NoteChange: 'NoteChange',
  NoteDelete: 'NoteDelete',
  LayoutOrganize: 'LayoutOrganize',
} as const

export type WorkflowHistoryEventT = keyof typeof WorkflowHistoryEvent

export const useWorkflowHistory = () => {
  const store = useStoreApi()
  const { store: workflowHistoryStore } = useWorkflowHistoryStore()
  const { t } = useTranslation()

  const [undoCallbacks, setUndoCallbacks] = useState<(() => void)[]>([])
  const [redoCallbacks, setRedoCallbacks] = useState<(() => void)[]>([])

  const onUndo = useCallback((callback: () => void) => {
    setUndoCallbacks(prev => [...prev, callback])
    return () => setUndoCallbacks(prev => prev.filter(cb => cb !== callback))
  }, [])

  const onRedo = useCallback((callback: () => void) => {
    setRedoCallbacks(prev => [...prev, callback])
    return () => setRedoCallbacks(prev => prev.filter(cb => cb !== callback))
  }, [])

  const undo = useCallback(() => {
    workflowHistoryStore.temporal.getState().undo()
    undoCallbacks.forEach(callback => callback())
  }, [undoCallbacks, workflowHistoryStore.temporal])

  const redo = useCallback(() => {
    workflowHistoryStore.temporal.getState().redo()
    redoCallbacks.forEach(callback => callback())
  }, [redoCallbacks, workflowHistoryStore.temporal])

  // Some events may be triggered multiple times in a short period of time.
  // We debounce the history state update to avoid creating multiple history states
  // with minimal changes.
  const saveStateToHistoryRef = useRef(debounce((event: WorkflowHistoryEventT, meta?: WorkflowHistoryEventMeta) => {
    workflowHistoryStore.setState({
      workflowHistoryEvent: event,
      workflowHistoryEventMeta: meta,
      nodes: store.getState().getNodes(),
      edges: store.getState().edges,
    })
  }, 500))

  const saveStateToHistory = useCallback((event: WorkflowHistoryEventT, meta?: WorkflowHistoryEventMeta) => {
    switch (event) {
      case WorkflowHistoryEvent.NoteChange:
        // Hint: Note change does not trigger when note text changes,
        // because the note editors have their own history states.
        saveStateToHistoryRef.current(event, meta)
        break
      case WorkflowHistoryEvent.NodeTitleChange:
      case WorkflowHistoryEvent.NodeDescriptionChange:
      case WorkflowHistoryEvent.NodeDragStop:
      case WorkflowHistoryEvent.NodeChange:
      case WorkflowHistoryEvent.NodeConnect:
      case WorkflowHistoryEvent.NodePaste:
      case WorkflowHistoryEvent.NodeDelete:
      case WorkflowHistoryEvent.EdgeDelete:
      case WorkflowHistoryEvent.EdgeDeleteByDeleteBranch:
      case WorkflowHistoryEvent.NodeAdd:
      case WorkflowHistoryEvent.NodeResize:
      case WorkflowHistoryEvent.NoteAdd:
      case WorkflowHistoryEvent.LayoutOrganize:
      case WorkflowHistoryEvent.NoteDelete:
        saveStateToHistoryRef.current(event, meta)
        break
      default:
        // We do not create a history state for every event.
        // Some events of reactflow may change things the user would not want to undo/redo.
        // For example: UI state changes like selecting a node.
        break
    }
  }, [])

  const getHistoryLabel = useCallback((event: WorkflowHistoryEventT) => {
    switch (event) {
      case WorkflowHistoryEvent.NodeTitleChange:
        return t('changeHistory.nodeTitleChange', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeDescriptionChange:
        return t('changeHistory.nodeDescriptionChange', { ns: 'workflow' })
      case WorkflowHistoryEvent.LayoutOrganize:
      case WorkflowHistoryEvent.NodeDragStop:
        return t('changeHistory.nodeDragStop', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeChange:
        return t('changeHistory.nodeChange', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeConnect:
        return t('changeHistory.nodeConnect', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodePaste:
        return t('changeHistory.nodePaste', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeDelete:
        return t('changeHistory.nodeDelete', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeAdd:
        return t('changeHistory.nodeAdd', { ns: 'workflow' })
      case WorkflowHistoryEvent.EdgeDelete:
      case WorkflowHistoryEvent.EdgeDeleteByDeleteBranch:
        return t('changeHistory.edgeDelete', { ns: 'workflow' })
      case WorkflowHistoryEvent.NodeResize:
        return t('changeHistory.nodeResize', { ns: 'workflow' })
      case WorkflowHistoryEvent.NoteAdd:
        return t('changeHistory.noteAdd', { ns: 'workflow' })
      case WorkflowHistoryEvent.NoteChange:
        return t('changeHistory.noteChange', { ns: 'workflow' })
      case WorkflowHistoryEvent.NoteDelete:
        return t('changeHistory.noteDelete', { ns: 'workflow' })
      default:
        return 'Unknown Event'
    }
  }, [t])

  return {
    store: workflowHistoryStore,
    saveStateToHistory,
    getHistoryLabel,
    undo,
    redo,
    onUndo,
    onRedo,
  }
}
