File size: 2,885 Bytes
af37c1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { useState, useRef, useCallback } from 'react'
import type { SimResult, SimStep } from '../types'

interface SimState {
  result: SimResult | null
  currentStepIndex: number
  isPlaying: boolean
  isLoading: boolean
  error: string | null
}

const SPEED_MS: Record<string, number> = { slow: 2000, normal: 1000, fast: 400 }

export function useSimulation() {
  const [state, setState] = useState<SimState>({
    result: null,
    currentStepIndex: -1,
    isPlaying: false,
    isLoading: false,
    error: null,
  })
  const [speed, setSpeed] = useState<'slow' | 'normal' | 'fast'>('normal')
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)

  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current)
      timerRef.current = null
    }
  }

  const startPlayback = useCallback((_result: SimResult, fromIndex = 0) => {
    stopTimer()
    setState(s => ({ ...s, currentStepIndex: fromIndex, isPlaying: true }))

    timerRef.current = setInterval(() => {
      setState(s => {
        if (!s.result) return s
        const next = s.currentStepIndex + 1
        if (next >= s.result.steps.length) {
          stopTimer()
          return { ...s, isPlaying: false }
        }
        return { ...s, currentStepIndex: next }
      })
    }, SPEED_MS[speed])
  }, [speed])

  const load = useCallback(async (
    fetcher: () => Promise<SimResult>,
  ) => {
    stopTimer()
    setState({ result: null, currentStepIndex: -1, isPlaying: false, isLoading: true, error: null })
    try {
      const result = await fetcher()
      setState({ result, currentStepIndex: 0, isPlaying: false, isLoading: false, error: null })
      startPlayback(result, 0)
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e)
      setState(s => ({ ...s, isLoading: false, error: message }))
    }
  }, [startPlayback])

  const pause = useCallback(() => {
    stopTimer()
    setState(s => ({ ...s, isPlaying: false }))
  }, [])

  const play = useCallback(() => {
    setState(s => {
      if (!s.result) return s
      startPlayback(s.result, s.currentStepIndex)
      return s
    })
  }, [startPlayback])

  const reset = useCallback(() => {
    stopTimer()
    setState(s => ({ ...s, currentStepIndex: 0, isPlaying: false }))
  }, [])

  const seekTo = useCallback((idx: number) => {
    stopTimer()
    setState(s => ({ ...s, currentStepIndex: idx, isPlaying: false }))
  }, [])

  const currentStep: SimStep | null =
    state.result && state.currentStepIndex >= 0
      ? state.result.steps[state.currentStepIndex] ?? null
      : null

  return {
    result: state.result,
    currentStep,
    currentStepIndex: state.currentStepIndex,
    isPlaying: state.isPlaying,
    isLoading: state.isLoading,
    error: state.error,
    speed,
    setSpeed,
    load,
    play,
    pause,
    reset,
    seekTo,
  }
}