@startuml WalkGuide_StateMachine
title State Machine Diagram — WalkGuide System (Full Architecture)

skinparam state {
  BackgroundColor WhiteSmoke
  BorderColor #5B6AB0
  FontColor Black
  ArrowColor #5B6AB0
  StartColor Black
  EndColor Black
}
skinparam note {
  BackgroundColor LightYellow
  BorderColor #999
}

' ============================================================
' TOP LEVEL: APP LAUNCH
' ============================================================
[*] --> AppLaunched : App dibuka

state AppLaunched {
  [*] --> CheckingServerUrl

  CheckingServerUrl : Cek SharedPreferences\nserver URL tersimpan?

  CheckingServerUrl --> ServerConnectScreen : [Tidak ada URL]
  CheckingServerUrl --> CheckingToken       : [URL tersedia]

  ServerConnectScreen : Input server URL\n(http://202.46.28.160:8080)
  ServerConnectScreen --> PingingServer : Submit URL

  PingingServer : GET /api/v1/auth/ping\n(cek koneksi ke backend)
  PingingServer --> ServerConnectScreen : [Ping Gagal / Timeout]
  PingingServer --> CheckingToken       : [Ping OK → simpan URL ke SharedPreferences]

  CheckingToken : Cek flutter_secure_storage\naccessToken ada?
  CheckingToken --> Unauthenticated : [Token Tidak Ada]
  CheckingToken --> ValidatingToken : [Token Ada]

  ValidatingToken : Verifikasi JWT\n(decode expiry lokal)
  ValidatingToken --> Unauthenticated   : [Token Expired / Invalid]
  ValidatingToken --> Authenticated     : [Token Valid → decode role]
}

' ============================================================
' UNAUTHENTICATED
' ============================================================
state Unauthenticated {
  [*] --> LoginScreen

  LoginScreen : Input email & password\n+ tombol ke RegisterScreen

  LoginScreen --> RegisterScreen  : Tap "Register"
  RegisterScreen --> LoginScreen  : Tap "Back to Login"

  RegisterScreen : Pilih role (ROLE_GUARDIAN / ROLE_USER)\nIsi email, password, displayName

  LoginScreen    --> ValidatingCredentials : Submit login
  RegisterScreen --> RegisteringUser       : Submit register

  ValidatingCredentials : POST /api/v1/auth/login\n{email, password}
  ValidatingCredentials --> LoginScreen             : [401 Unauthorized → tampil error]
  ValidatingCredentials --> StoringAuthData         : [200 OK + JWT]

  RegisteringUser : POST /api/v1/auth/register\n{email, password, role, displayName}
  RegisteringUser --> LoginScreen     : [400 / Email sudah terdaftar]
  RegisteringUser --> StoringAuthData : [201 Created + JWT]

  StoringAuthData : Simpan accessToken + refreshToken\nke flutter_secure_storage\nUpdate FCM token → PUT /auth/fcm-token\nWebSocketService.connect(accessToken)
  StoringAuthData --> DecodingRole : JWT tersimpan

  DecodingRole : Ekstrak role dari JWT claims
  DecodingRole --> [*]
}

note on link
  Login Sukses
  200 OK + JWT
  (access + refresh)
end note

Unauthenticated --> Authenticated : [Login / Register Sukses → role decoded]

' ============================================================
' AUTHENTICATED (role router)
' ============================================================
state Authenticated {
  [*] --> CheckingPairing

  CheckingPairing : GET /shared/pairing/status\ncek status pairing user ini

  CheckingPairing --> UserShell     : [Role = ROLE_USER]
  CheckingPairing --> GuardianShell : [Role = ROLE_GUARDIAN]
}

' ============================================================
' USER SHELL
' ============================================================
state UserShell {

  [*] --> UserIdle

  UserIdle : Bottom nav 6 tabs aktif\nSTT always-listening di background\nWebSocket subscribe /queue/notif/{userId}
  note right of UserIdle
    Voice commands aktif:\n"Start Walkguide", "Open SOS",\n"Call Guardian", "Where Am I", dll
  end note

  ' --- PAIRING CHECK ---
  UserIdle --> PairingScreen_User : [Belum paired → TTS peringatan]

  state PairingScreen_User {
    [*] --> WaitingPairingInvite
    WaitingPairingInvite : GET /shared/pairing/status\nTampil uniqueUserId 12-char\n"Share this ID with your Guardian"
    WaitingPairingInvite --> ReceivingInvite   : FCM: PAIRING_INVITE diterima
    ReceivingInvite --> WaitingPairingInvite   : Tap "Reject" → POST /pairing/respond {accept:false}
    ReceivingInvite --> PairingAccepted        : Tap "Accept" → POST /pairing/respond {accept:true}
    PairingAccepted : Backend seed 14 VoiceCommands\n+ 5 Shortcuts + AiConfig defaults\nFCM ke Guardian: "Pairing accepted"\nTTS: "Pairing successful. Guardian connected."
    PairingAccepted --> [*]
  }

  PairingScreen_User --> UserIdle : Pairing berhasil / lewati

  ' --- WALKGUIDE ---
  UserIdle --> WalkGuideActive : Tap "Start Walk Guide"\nATAU voice "Start Walkguide"\nATAU Volume Down button

  state WalkGuideActive {
    [*] --> InitializingCamera

    InitializingCamera : camera.startImageStream()\nRequest permissions (kamera, lokasi)\nPOST /user/walkguide/start → ActivityLog: WALKGUIDE_START
    InitializingCamera --> [*] : [Permission Denied → kembali ke UserIdle]

    InitializingCamera --> DetectionLoop : Kamera siap\nTTS: "3... 2... 1... WalkGuide started."

    state DetectionLoop {
      [*] --> CapturingFrame

      CapturingFrame : Terima CameraImage (YUV420)\nThrottle: maxInferenceFps (default 5fps)
      CapturingFrame --> ProcessingFrame : [YoloDetector tidak sedang running → kirim frame]
      CapturingFrame --> CapturingFrame  : [YoloDetector sedang running → skip frame ini]

      ProcessingFrame : YUV420→RGB→Resize 640×640\nNormalize 0.0-1.0\ninterpreter.run() [Isolate terpisah]\nPost-process + NMS\nFilter by confidenceThreshold

      ProcessingFrame --> EvaluatingResult : Inference selesai

      EvaluatingResult : ObstacleAnalyzer.prioritize(results)\nTentukan direction (LEFT/CENTER/RIGHT)\nEstimasi distance (Very Close/Close/Medium/Far)

      EvaluatingResult --> ObstacleDetected    : [Confidence ≥ threshold & ada deteksi]
      EvaluatingResult --> NoObstacleDetected  : [Confidence < threshold / tidak ada deteksi]

      ObstacleDetected : Build TTS: "Caution! {label} {direction}. {distance}. Please stop."\nttsService.speakImmediate()\nhapticService.obstacleVeryClose()\nDebounce 3 detik (jangan log yang sama terus)\nPOST /user/obstacle {label, confidence, direction, dist, lat, lng}

      NoObstacleDetected : Lanjut deteksi

      ObstacleDetected   --> CapturingFrame : Feedback selesai
      NoObstacleDetected --> CapturingFrame : Lanjut deteksi

      ' Location update loop (parallel, setiap 5 detik)
      CapturingFrame --> SendingLocation : [Setiap 5 detik]
      SendingLocation : geolocator.getCurrentPosition()\nPOST /user/location {lat, lng, accuracy, speed, heading}\nBackend: simpan LocationHistory\nWebSocket broadcast → Guardian map\nGeofenceService.checkGeofence()
      SendingLocation --> CapturingFrame : Location terkirim
    }

    DetectionLoop --> SavingSession : Tap "Stop Walk Guide"\nATAU voice "Stop Walkguide"

    SavingSession : camera.stopImageStream()\nPOST /user/walkguide/stop → ActivityLog: WALKGUIDE_STOP\nLocation interval → 30 detik
    SavingSession --> [*]
  }

  WalkGuideActive --> UserIdle : Sesi WalkGuide selesai

  ' --- SOS ---
  UserIdle --> SosFlow : Tap SOS tab\nATAU voice "Send SOS"\nATAU hardware shortcut

  state SosFlow {
    [*] --> SosConfirm
    SosConfirm : SosScreen: tombol SOS merah besar\nTTS: "SOS screen. Press the big red button\nor say 'Send SOS' to alert your Guardian."
    SosConfirm --> TriggeringSos : Tap SOS button / voice "Send SOS"
    TriggeringSos : Get GPS position\nPOST /user/sos {triggerType, lat, lng}\nBackend: save SosEvent TRIGGERED\nFCM HIGH PRIORITY ke Guardian: "🚨 SOS ALERT!"\nWebSocket → /queue/sos/{guardianId}\nTTS: "SOS sent. Guardian has been alerted."\nhapticService.sosTriggered()
    TriggeringSos --> WaitingAcknowledge : SOS terkirim
    WaitingAcknowledge : Tunggu respons Guardian\nTampil status "Waiting for Guardian..."
    WaitingAcknowledge --> SosAcknowledged : FCM: Guardian acknowledge SOS\nTTS: "Your Guardian has acknowledged.\nHelp is coming."
    SosAcknowledged --> [*]
  }

  SosFlow --> UserIdle : Kembali ke home

  ' --- NOTIFICATIONS ---
  UserIdle --> NotificationFlow : Tap Notifications tab\nATAU voice "Open Notifications"\nATAU FCM/WebSocket masuk

  state NotificationFlow {
    [*] --> ViewingNotifications
    ViewingNotifications : GET /user/notifications\nGET /user/notifications/unread-count\nTampil list: TEXT / VOICE_NOTE\nBadge unread count\nTTS: "Notifications. You have {N} unread messages."
    ViewingNotifications --> ReadingAllTts   : Tap "Read All" / voice "Read All My Notifications"
    ViewingNotifications --> ReadingOneTts   : Tap satu notifikasi
    ReadingAllTts : Iterate unread list:\n- TEXT: TTS "From Guardian on {date}: {content}"\n- VOICE_NOTE: just_audio.play(voiceNoteUrl)\nPUT /user/notifications/{id}/read setelah selesai
    ReadingOneTts : TTS / play satu notifikasi\nPUT /user/notifications/{id}/read
    ReadingAllTts   --> ViewingNotifications : Semua notif dibaca
    ReadingOneTts   --> ViewingNotifications : Selesai
    ViewingNotifications --> MarkingAllRead  : Tap "Mark All Read"\nPUT /user/notifications/mark-all-read
    MarkingAllRead --> ViewingNotifications  : Done
  }

  NotificationFlow --> UserIdle : Kembali

  ' --- NAVIGATION MODE ---
  UserIdle --> NavigationModeActive : Tap Navigate tab\nATAU voice "Open Navigation"

  state NavigationModeActive {
    [*] --> NavigationIdle
    NavigationIdle : FlutterMap (OpenStreetMap tiles)\nTTS: "Navigation mode. Say a destination."
    NavigationIdle --> SearchingDestination : Tap search bar / voice "Navigate to {place}"
    SearchingDestination : OSM Nominatim API\nGET nominatim.openstreetmap.org/search?q=...
    SearchingDestination --> NavigatingRoute : Lokasi ditemukan → OSRM routing
    NavigatingRoute : OSRM turn-by-turn route\nTTS setiap instruksi: "In 50 meters, turn right"\ngeolocator: GPS real-time\nPOST /user/location setiap 5 detik
    NavigatingRoute --> NavigationIdle : Sampai tujuan / Stop
  }

  NavigationModeActive --> UserIdle : Kembali

  ' --- CALL ---
  UserIdle --> OutgoingCallFlow : Tap "Call Guardian"\nATAU voice "Call Guardian"\nATAU Volume Up button

  state OutgoingCallFlow {
    [*] --> RequestingAgoraToken
    RequestingAgoraToken : POST /shared/call/token\nGenerate Agora RTC token\nPOST /shared/call/notify → FCM ke Guardian "Incoming Call"
    RequestingAgoraToken --> CallingGuardian : Token dapat, join Agora channel
    CallingGuardian : CallScreen: "Calling Guardian..." + animasi\nAgoraRtcEngine.joinChannel()
    CallingGuardian --> InCall : Guardian pick up → CallConnected
    CallingGuardian --> UserIdle : Guardian decline / timeout 30 detik
    InCall : Timer durasi call\nTombol: Mute, Speaker, End Call\nTTS: "Connected to Guardian"
    InCall --> UserIdle : Tap End Call
  }

  UserIdle --> IncomingCallHandled : FCM INCOMING_CALL diterima dari Guardian

  state IncomingCallHandled {
    [*] --> ShowingIncomingCall
    ShowingIncomingCall : IncomingCallScreen\nTTS: "Incoming call from Guardian"\nHaptic vibration\nTombol Accept (hijau) + Decline (merah)\nAuto-answer setelah 30 detik
    ShowingIncomingCall --> InCallSession : Accept
    ShowingIncomingCall --> UserIdle      : Decline
    InCallSession : Agora RTC connected\nTimer + Mute + Speaker + End
    InCallSession --> UserIdle : End call
  }

  ' --- SETTINGS ---
  UserIdle --> UserSettingsFlow : Tap Settings tab\nATAU voice "Open Settings"

  state UserSettingsFlow {
    [*] --> ViewingSettings
    ViewingSettings : TTS Settings (read-only pitch/speed)\nPairing status link\nManual/Instructions link\nNo-Guardian Warning toggle\nAccount info + Logout
    ViewingSettings --> UpdatingTtsLanguage : Toggle ID/EN → PUT /user/settings
    ViewingSettings --> ViewingManual       : Tap "Instructions"
    ViewingSettings --> LoggingOut          : Tap Logout
    UpdatingTtsLanguage --> ViewingSettings : Saved
    ViewingManual : ManualScreen\nSemua voice commands + shortcuts\nTombol "Hear Instructions"\nTTS setiap instruksi 1-per-1
    ViewingManual --> ViewingSettings : Back
    LoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocketService.disconnect()\nGoRouter → /login
    LoggingOut --> [*]
  }

  UserSettingsFlow --> UserIdle   : Kembali
  UserSettingsFlow --> [*]        : Logout

  ' --- ACTIVITY LOG ---
  UserIdle --> ActivityLogFlow : Tap Activity tab\nATAU voice "Open Activity Log"

  state ActivityLogFlow {
    [*] --> ViewingActivityLog
    ViewingActivityLog : GET /user/activity-logs (paginated)\nInfinite scroll\nFilter by type/date\nTTS: "Activity log. {N} activities today."
  }

  ActivityLogFlow --> UserIdle : Kembali
}

' ============================================================
' GUARDIAN SHELL
' ============================================================
state GuardianShell {

  [*] --> GuardianIdle

  GuardianIdle : GuardianDashboardScreen HOME\nCard: User status (online/offline, battery)\nCard: Last location (Nominatim reverse)\nCard: Unread SOS (badge merah)\nCard: Recent 5 activity logs\nQuick actions: Send Message, Call User, View Map\nWebSocket subscribe:\n  /topic/location/{pairedUserId}\n  /queue/sos/{guardianId}

  ' --- PAIRING (Guardian side) ---
  GuardianIdle --> GuardianPairingFlow : Tap "Manage Pairing"\nATAU belum punya paired user

  state GuardianPairingFlow {
    [*] --> EnteringUserId
    EnteringUserId : Input uniqueUserId 12-char\nmilik paired User
    EnteringUserId --> SendingInvite : Tap "Send Invite"
    SendingInvite : POST /shared/pairing/invite {uniqueUserId}\nBackend: buat PairingRelation PENDING\nFCM ke User: "Pairing Request"
    SendingInvite --> WaitingUserResponse : Invite terkirim
    WaitingUserResponse : Tunggu User accept/reject
    WaitingUserResponse --> PairingSuccess : FCM: User accepted\nBackend seed VoiceCommands + Shortcuts + AiConfig
    WaitingUserResponse --> EnteringUserId  : FCM: User rejected
    PairingSuccess : TTS: "Pairing successful. User is now connected."\nNavigate → GuardianIdle
    PairingSuccess --> [*]
  }

  GuardianPairingFlow --> GuardianIdle : Selesai

  ' --- MAP / LIVE TRACKING ---
  GuardianIdle --> GuardianMapView : Tap "View Map"\nATAU quick action map

  state GuardianMapView {
    [*] --> ViewingLiveMap
    ViewingLiveMap : FlutterMap + live location marker\nWebSocket /topic/location/{userId}\nGeofence circle overlay (jika enabled)\nRoute history polyline (location_history)
    ViewingLiveMap --> ViewingLiveMap : [WebSocket update → marker pindah]
  }

  GuardianMapView --> GuardianIdle : Kembali

  ' --- ACTIVITY LOGS ---
  GuardianIdle --> GuardianActivityLog : Tap "View Activity Logs"

  state GuardianActivityLog {
    [*] --> ViewingUserLogs
    ViewingUserLogs : GET /guardian/activity-logs (paginated)\nGET /guardian/obstacle-logs\nFilter by type, date
  }

  GuardianActivityLog --> GuardianIdle : Kembali

  ' --- SEND NOTIFICATION ---
  GuardianIdle --> GuardianSendNotif : Tap "Send Notification"

  state GuardianSendNotif {
    [*] --> ComposingNotif
    ComposingNotif : Toggle: TEXT / VOICE NOTE
    ComposingNotif --> SendingTextNotif   : Ketik pesan → Send
    ComposingNotif --> RecordingVoiceNote : Tahan tombol Record
    SendingTextNotif : POST /guardian/notifications/send\n{type: TEXT, content}\nBackend: FCM ke User + WebSocket\nUser TTS: "New message from Guardian"
    RecordingVoiceNote : record package → audio file\nPreview → Upload → POST send\n{type: VOICE_NOTE, voiceNoteUrl}
    SendingTextNotif   --> ComposingNotif : Sent
    RecordingVoiceNote --> ComposingNotif : Sent
  }

  GuardianSendNotif --> GuardianIdle : Kembali

  ' --- AI CONFIG ---
  GuardianIdle --> GuardianAiConfig : Tap "Configure AI Settings"

  state GuardianAiConfig {
    [*] --> EditingAiConfig
    EditingAiConfig : GET /guardian/ai-config\nSlider confidenceThreshold (0.3-0.9)\nSlider alertDistanceClose (0.5-3m)\nSlider alertDistanceMedium (1-6m)\nDropdown maxInferenceFps (1,2,3,5,10)\nMulti-select enabled labels
    EditingAiConfig --> SavingAiConfig : Tap Save
    SavingAiConfig : PUT /guardian/ai-config\nBackend: update + FCM ke User\n"Guardian updated AI settings"
    SavingAiConfig --> EditingAiConfig : Saved
  }

  GuardianAiConfig --> GuardianIdle : Kembali

  ' --- VOICE COMMANDS CONFIG ---
  GuardianIdle --> GuardianVoiceCmdConfig : Tap "Voice Commands"

  state GuardianVoiceCmdConfig {
    [*] --> EditingVoiceCommands
    EditingVoiceCommands : GET /guardian/voice-commands\nList 14 commands\nEdit trigger phrase\nToggle enabled/disabled
    EditingVoiceCommands --> SavingVoiceCmd : Save
    SavingVoiceCmd : PUT /guardian/voice-commands\nFCM ke User: "Voice commands updated"
    SavingVoiceCmd --> EditingVoiceCommands : Saved
  }

  GuardianVoiceCmdConfig --> GuardianIdle : Kembali

  ' --- HARDWARE SHORTCUTS ---
  GuardianIdle --> GuardianShortcutConfig : Tap "Shortcuts"

  state GuardianShortcutConfig {
    [*] --> EditingShortcuts
    EditingShortcuts : GET /guardian/shortcuts\nList 5 shortcuts (Vol Up, Vol Down, dll)\n"Unassigned" jika belum di-set
    EditingShortcuts --> CapturingButton : Tap "Configure" per shortcut
    CapturingButton : FCM ke User's phone "enter capture mode"\nUser tekan tombol fisik → keyCode\nPUT /user/shortcuts {shortcutKey, buttonCode}
    CapturingButton --> EditingShortcuts : Button captured & saved
  }

  GuardianShortcutConfig --> GuardianIdle : Kembali

  ' --- GEOFENCE ---
  GuardianIdle --> GuardianGeofenceConfig : Tap "Geofence"

  state GuardianGeofenceConfig {
    [*] --> EditingGeofence
    EditingGeofence : FlutterMap: tap untuk set center\nSlider radius (50m - 5000m)\nToggle enable/disable geofence
    EditingGeofence --> SavingGeofence : Tap Save
    SavingGeofence : PUT /guardian/geofence\nBackend: simpan GeofenceConfig
    SavingGeofence --> EditingGeofence : Saved
  }

  GuardianGeofenceConfig --> GuardianIdle : Kembali

  ' --- SOS HANDLING ---
  GuardianIdle --> SosAlertReceived : FCM HIGH PRIORITY / WebSocket SOS alert\n/queue/sos/{guardianId}

  state SosAlertReceived {
    [*] --> DisplayingSosAlert
    DisplayingSosAlert : GuardianDashboard: highlight merah\nMap otomatis buka ke lokasi User\nTampil: "🚨 {User} needs help! Location: lat,lng"\nGET /guardian/sos-events
    DisplayingSosAlert --> AcknowledgingSos : Tap "Acknowledge"
    AcknowledgingSos : PUT /guardian/sos/{id}/acknowledge\nBackend: status → ACKNOWLEDGED\nFCM ke User: "Guardian is on the way"\nUser TTS: "Help is coming."
    AcknowledgingSos --> [*]
  }

  SosAlertReceived --> GuardianIdle : Selesai

  ' --- CALL (Guardian inisiasi) ---
  GuardianIdle --> GuardianOutgoingCall : Tap "Call User"

  state GuardianOutgoingCall {
    [*] --> GuardianRequestToken
    GuardianRequestToken : POST /shared/call/token\nPOST /shared/call/notify → FCM ke User "Incoming Call"
    GuardianRequestToken --> GuardianCalling : Join Agora channel
    GuardianCalling : "Calling User..." + ring animasi
    GuardianCalling --> GuardianInCall : User accept
    GuardianCalling --> GuardianIdle   : User decline / timeout
    GuardianInCall : Audio call aktif\nMute + Speaker + End
    GuardianInCall --> GuardianIdle : End call
  }

  GuardianOutgoingCall --> GuardianIdle : Selesai

  ' --- INCOMING CALL (Guardian terima) ---
  GuardianIdle --> GuardianIncomingCall : FCM INCOMING_CALL dari User

  state GuardianIncomingCall {
    [*] --> GuardianShowIncomingCall
    GuardianShowIncomingCall : IncomingCallScreen\nTTS: "Incoming call from User"\nAccept (hijau) / Decline (merah)
    GuardianShowIncomingCall --> GuardianInCallSession : Accept
    GuardianShowIncomingCall --> GuardianIdle           : Decline
    GuardianInCallSession : Agora RTC connected
    GuardianInCallSession --> GuardianIdle : End call
  }

  GuardianIncomingCall --> GuardianIdle : Selesai

  ' --- LOGOUT ---
  GuardianIdle --> GuardianLoggingOut : Tap Logout

  GuardianLoggingOut : POST /auth/logout {refreshToken}\nSecureStorage.clearAll()\nWebSocket.disconnect()
  GuardianLoggingOut --> [*]
}

' ============================================================
' GLOBAL LOGOUT / TOKEN REFRESH
' ============================================================
UserShell    --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)
GuardianShell --> Unauthenticated : Logout / Token Expired (401 → refresh gagal)

note as TokenRefreshNote
  **Token Refresh (AuthInterceptor):**
  Setiap request: inject "Authorization: Bearer {accessToken}"
  Jika 401 → POST /auth/refresh {refreshToken}
  Jika refresh OK → retry request dengan token baru
  Jika refresh 401 → navigate ke /login
  Access token: 1 jam | Refresh token: 30 hari
end note

' ============================================================
' GEOFENCE EXIT (Parallel event)
' ============================================================
note as GeofenceNote
  **Geofence Alert (Backend event):**
  Setiap POST /user/location (saat WalkGuide aktif, 5 detik):
  Backend: GeofenceService.checkGeofence()
  → Haversine distance dari center ke current position
  → Jika keluar radius: FCM ke Guardian "User has left geofence!"
  → ActivityLog: GEOFENCE_EXIT
  → Guardian: notifikasi di GuardianDashboard
end note

@enduml
