; ClawX Custom NSIS Installer/Uninstaller Script
;
; Install: enables long paths, adds resources\cli to user PATH for openclaw CLI.
; Uninstall: removes the PATH entry and optionally deletes user data.

; When customCheckAppRunning is defined, electron-builder skips its conditional
; !include for getProcessInfo.nsh and the "Var pid" declaration. We must do
; both ourselves so ${GetProcessInfo} and $pid are available.
!ifndef getProcessInfo_included
  !define getProcessInfo_included
  !include "getProcessInfo.nsh"
  Var pid
!endif
!ifndef nsProcess_included
  !define nsProcess_included
  !include "nsProcess.nsh"
!endif

!macro customCheckAppRunning
  ${GetProcessInfo} 0 $pid $1 $2 $3 $4
  ${if} $3 != "${APP_EXECUTABLE_FILENAME}"
    ${if} ${isUpdated}
      # allow app to exit without explicit kill
      Sleep 300
    ${endIf}

    # Instead of launching cmd.exe /c tasklist, use the nsProcess plugin directly for all environments.
    # This prevents the brief black cmd window from flashing.
    ${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0

    ${if} $R0 == 0
      ${if} ${isUpdated}
        # allow app to exit without explicit kill
        Sleep 1000
        Goto doStopProcess
      ${endIf}
      MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(appRunning)" /SD IDOK IDOK doStopProcess
      Quit

      doStopProcess:
      DetailPrint `Closing running "${PRODUCT_NAME}"...`

      # Silently kill the process using nsProcess instead of taskkill / cmd.exe
      ${nsProcess::KillProcess} "${APP_EXECUTABLE_FILENAME}" $R0
      
      # to ensure that files are not "in-use"
      Sleep 300

      # Retry counter
      StrCpy $R1 0

      loop:
        IntOp $R1 $R1 + 1

        ${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
        ${if} $R0 == 0
          # wait to give a chance to exit gracefully
          Sleep 1000
          ${nsProcess::KillProcess} "${APP_EXECUTABLE_FILENAME}" $R0
          
          ${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
          ${If} $R0 == 0
            DetailPrint `Waiting for "${PRODUCT_NAME}" to close.`
            Sleep 2000
          ${else}
            Goto not_running
          ${endIf}
        ${else}
          Goto not_running
        ${endIf}

        # App likely running with elevated permissions.
        # Ask user to close it manually
        ${if} $R1 > 1
          MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(appCannotBeClosed)" /SD IDCANCEL IDRETRY loop
          Quit
        ${else}
          Goto loop
        ${endIf}
      not_running:
        nsProcess::Unload
    ${endIf}
  ${endIf}
!macroend

!macro customInstall
  ; Enable Windows long path support (Windows 10 1607+ / Windows 11).
  ; pnpm virtual store paths can exceed the default MAX_PATH limit of 260 chars.
  ; Writing to HKLM requires admin privileges; on per-user installs without
  ; elevation this call silently fails — no crash, just no key written.
  WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Control\FileSystem" "LongPathsEnabled" 1

  ; Add resources\cli to the current user's PATH for openclaw CLI.
  ; Read current PATH, skip if already present, append otherwise.
  ReadRegStr $0 HKCU "Environment" "Path"
  StrCmp $0 "" _ci_setNew

  ; Check if our CLI dir is already in PATH
  Push "$INSTDIR\resources\cli"
  Push $0
  Call _ci_StrContains
  Pop $1
  StrCmp $1 "" 0 _ci_done

  ; Append to existing PATH
  StrCpy $0 "$0;$INSTDIR\resources\cli"
  Goto _ci_write

  _ci_setNew:
    StrCpy $0 "$INSTDIR\resources\cli"

  _ci_write:
    WriteRegExpandStr HKCU "Environment" "Path" $0
    ; Broadcast WM_SETTINGCHANGE so running Explorer/terminals pick up the change
    SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment" /TIMEOUT=500

  _ci_done:
!macroend

; Helper: check if $R0 (needle) is found within $R1 (haystack).
; Pushes needle then haystack before call; pops result (needle if found, "" if not).
Function _ci_StrContains
  Exch $R1 ; haystack
  Exch
  Exch $R0 ; needle
  Push $R2
  Push $R3
  Push $R4

  StrLen $R3 $R0
  StrLen $R4 $R1
  IntOp $R4 $R4 - $R3

  StrCpy $R2 0
  _ci_loop:
    IntCmp $R2 $R4 0 0 _ci_notfound
    StrCpy $1 $R1 $R3 $R2
    StrCmp $1 $R0 _ci_found
    IntOp $R2 $R2 + 1
    Goto _ci_loop

  _ci_found:
    StrCpy $R0 $R0
    Goto _ci_end

  _ci_notfound:
    StrCpy $R0 ""

  _ci_end:
    Pop $R4
    Pop $R3
    Pop $R2
    Pop $R1
    Exch $R0
FunctionEnd

!macro customUnInstall
  ; Remove resources\cli from user PATH
  ReadRegStr $0 HKCU "Environment" "Path"
  StrCmp $0 "" _cu_pathDone

  ; Remove our entry (with leading or trailing semicolons)
  Push $0
  Push "$INSTDIR\resources\cli"
  Call un._cu_RemoveFromPath
  Pop $0
  WriteRegExpandStr HKCU "Environment" "Path" $0
  SendMessage ${HWND_BROADCAST} ${WM_SETTINGCHANGE} 0 "STR:Environment" /TIMEOUT=500

  _cu_pathDone:

  ; Ask user if they want to completely remove all user data
  MessageBox MB_YESNO|MB_ICONQUESTION \
    "Do you want to completely remove all ClawX user data?$\r$\n$\r$\nThis will delete:$\r$\n  • .openclaw folder (configuration & skills)$\r$\n  • AppData\Local\clawx (local app data)$\r$\n  • AppData\Roaming\clawx (roaming app data)$\r$\n$\r$\nSelect 'No' to keep your data for future reinstallation." \
    /SD IDNO IDYES _cu_removeData IDNO _cu_skipRemove

  _cu_removeData:
    ; --- Always remove current user's data first ---
    RMDir /r "$PROFILE\.openclaw"
    RMDir /r "$LOCALAPPDATA\clawx"
    RMDir /r "$APPDATA\clawx"

    ; --- For per-machine (all users) installs, enumerate all user profiles ---
    StrCpy $R0 0

  _cu_enumLoop:
    EnumRegKey $R1 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $R0
    StrCmp $R1 "" _cu_enumDone

    ReadRegStr $R2 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$R1" "ProfileImagePath"
    StrCmp $R2 "" _cu_enumNext

    ExpandEnvStrings $R2 $R2
    StrCmp $R2 $PROFILE _cu_enumNext

    RMDir /r "$R2\.openclaw"
    RMDir /r "$R2\AppData\Local\clawx"
    RMDir /r "$R2\AppData\Roaming\clawx"

  _cu_enumNext:
    IntOp $R0 $R0 + 1
    Goto _cu_enumLoop

  _cu_enumDone:
  _cu_skipRemove:
!macroend

; Uninstaller helper: remove a substring from a semicolon-delimited PATH string.
; Push haystack, push needle before call; pops cleaned string.
Function un._cu_RemoveFromPath
  Exch $R0 ; needle
  Exch
  Exch $R1 ; haystack

  ; Try removing ";needle" (entry in the middle or end)
  Push "$R1"
  Push ";$R0"
  Call un._ci_StrReplace
  Pop $R1

  ; Try removing "needle;" (entry at the start)
  Push "$R1"
  Push "$R0;"
  Call un._ci_StrReplace
  Pop $R1

  ; Try removing exact match (only entry)
  StrCmp $R1 $R0 0 +2
    StrCpy $R1 ""

  Pop $R0
  Exch $R1
FunctionEnd

; Uninstaller helper: remove first occurrence of needle from haystack.
; Push haystack, push needle; pops result.
Function un._ci_StrReplace
  Exch $R0 ; needle
  Exch
  Exch $R1 ; haystack
  Push $R2
  Push $R3
  Push $R4
  Push $R5

  StrLen $R3 $R0
  StrLen $R4 $R1
  StrCpy $R5 ""
  StrCpy $R2 0

  _usr_loop:
    IntCmp $R2 $R4 _usr_done _usr_done
    StrCpy $1 $R1 $R3 $R2
    StrCmp $1 $R0 _usr_found
    StrCpy $1 $R1 1 $R2
    StrCpy $R5 "$R5$1"
    IntOp $R2 $R2 + 1
    Goto _usr_loop

  _usr_found:
    ; Copy the part after the needle
    IntOp $R2 $R2 + $R3
    StrCpy $1 $R1 "" $R2
    StrCpy $R5 "$R5$1"

  _usr_done:
    StrCpy $R1 $R5
    Pop $R5
    Pop $R4
    Pop $R3
    Pop $R2
    Pop $R0
    Exch $R1
FunctionEnd
