;
; NSIS installer for QuickSearch.
;
; Compiled by packaging/build-installer.sh, which supplies every define below
; and stages the files this installs. Running makensis on this file by hand
; will fail on the !error checks rather than build something half-configured.
;
;   VERSION       workspace version, e.g. 0.9.1
;   VERSION_QUAD  the same padded to a.b.c.d, which is all VIProductVersion takes
;   STAGE         directory holding the exact files to install
;   OUTFILE       path of the installer to write
;
; makensis runs on Linux, so the installer comes out of the same cross-compile
; job as the .zip and no Windows machine is involved anywhere in the pipeline.
; That also means file paths below use forward slashes: they are read by
; makensis on the build host, unlike $INSTDIR paths, which are Windows strings.

!ifndef VERSION | VERSION_QUAD | STAGE | OUTFILE
    !error "build this with packaging/build-installer.sh, which defines VERSION, VERSION_QUAD, STAGE and OUTFILE"
!endif

Unicode true
; The payload is two ~20 MB binaries; solid LZMA is worth the compression time.
SetCompressor /SOLID lzma

!include "MUI2.nsh"
!include "LogicLib.nsh"
!include "FileFunc.nsh"
!include "x64.nsh"

!define APP        "QuickSearch"
!define PUBLISHER  "Jeremy <jeremy@karsttech.com>"
!define HOMEPAGE   "https://code.karsttech.com/jeremy/quick_search"
; Where Add/Remove Programs looks, and where an upgrade finds the directory the
; previous version went into.
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP}"

Name "${APP} ${VERSION}"
OutFile "${OUTFILE}"
InstallDir "$PROGRAMFILES64\${APP}"
; Program Files and HKLM both need elevation, and it is what puts the shortcuts
; in front of every account on the machine rather than just the installing one.
RequestExecutionLevel admin
ShowInstDetails show
ShowUninstDetails show

VIProductVersion "${VERSION_QUAD}"
VIAddVersionKey "ProductName"     "${APP}"
VIAddVersionKey "ProductVersion"  "${VERSION}"
VIAddVersionKey "FileVersion"     "${VERSION_QUAD}"
VIAddVersionKey "CompanyName"     "${PUBLISHER}"
VIAddVersionKey "LegalCopyright"  "GPL-3.0-or-later"
VIAddVersionKey "FileDescription" "${APP} ${VERSION} installer"

!define MUI_ABORTWARNING
!define MUI_ICON   "${STAGE}/quicksearch.ico"
!define MUI_UNICON "${STAGE}/quicksearch.ico"

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${STAGE}/LICENSE.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
; The payload is two binaries and three documents, so this install finishes in
; about a second - fast enough that people reported it as a failure. The finish
; page is the only place that can say otherwise, so it lists what was installed
; and where instead of the stock "Setup was completed successfully."
; Kept to six lines: the finish page's text field is a fixed height, and MUI
; clips rather than scrolls what does not fit.
!define MUI_FINISHPAGE_TITLE "${APP} ${VERSION} is installed"
!define MUI_FINISHPAGE_TEXT "Installed into $INSTDIR:$\r$\n\
    quicksearch.exe (the app), quicksearch-cli.exe (terminal search),$\r$\n\
    README.md, LICENSE.txt and config_example.toml.$\r$\n$\r$\n\
    Your settings and search index are created on first run, under your \
    own account. Upgrading and uninstalling leave both alone."
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Run ${APP}"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchAsUser
!insertmacro MUI_PAGE_FINISH

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

; ------------------------------------------------------------------ init ----

Function .onInit
    ${IfNot} ${RunningX64}
        MessageBox MB_OK|MB_ICONSTOP \
            "${APP} is 64-bit only and this is a 32-bit Windows installation." /SD IDOK
        Abort
    ${EndIf}
    ; The installer stub is 32-bit (it runs anywhere), so without this every
    ; HKLM write below would be redirected into Wow6432Node - out of sight of
    ; 64-bit Add/Remove Programs and of the lookup two lines down.
    SetRegView 64
    ; Shortcuts and the uninstall entry are machine-wide, matching where the
    ; files go.
    SetShellVarContext all

    ; Deliberately not InstallDirRegKey: that is read before .onInit runs, which
    ; is before SetRegView 64, so it would look in the wrong registry view.
    ; $INSTDIR still holds the InstallDir default unless /D= overrode it on the
    ; command line, and an explicit /D= must win over the previous location.
    ${If} $INSTDIR == "$PROGRAMFILES64\${APP}"
        ReadRegStr $0 HKLM "${UNINST_KEY}" "InstallLocation"
        ${If} $0 != ""
            StrCpy $INSTDIR $0
        ${EndIf}
    ${EndIf}
FunctionEnd

Function un.onInit
    SetRegView 64
    SetShellVarContext all
FunctionEnd

; -------------------------------------------------------------- sections ----

Section "!${APP}" SecApp
    SectionIn RO
    SetOutPath "$INSTDIR"

    ; Windows will not replace a running executable, and the File commands below
    ; would stop halfway through with a write error. Delete fails on a mapped
    ; image and succeeds quietly when there is nothing there, which makes it a
    ; plugin-free "is it still running?" test.
    ClearErrors
    Delete "$INSTDIR\quicksearch.exe"
    Delete "$INSTDIR\quicksearch-cli.exe"
    ${If} ${Errors}
        MessageBox MB_OK|MB_ICONSTOP \
            "${APP} is still running. Close it and start this installer again." /SD IDOK
        Abort
    ${EndIf}

    File "${STAGE}/quicksearch.exe"
    File "${STAGE}/quicksearch-cli.exe"
    File "${STAGE}/quicksearch.ico"
    File "${STAGE}/README.md"
    File "${STAGE}/LICENSE.txt"
    File "${STAGE}/config_example.toml"
    ; No config.toml is installed, for the same reason the .deb ships none: one
    ; sitting next to the binary is portable mode, and it would override the
    ; personal config of every account on the machine. The app writes
    ; %APPDATA%\quicksearch\config.toml on first run instead.

    WriteUninstaller "$INSTDIR\uninstall.exe"

    WriteRegStr HKLM "${UNINST_KEY}" "DisplayName"          "${APP}"
    WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion"       "${VERSION}"
    WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon"          "$INSTDIR\quicksearch.ico"
    WriteRegStr HKLM "${UNINST_KEY}" "Publisher"            "${PUBLISHER}"
    WriteRegStr HKLM "${UNINST_KEY}" "URLInfoAbout"         "${HOMEPAGE}"
    WriteRegStr HKLM "${UNINST_KEY}" "InstallLocation"      "$INSTDIR"
    WriteRegStr HKLM "${UNINST_KEY}" "UninstallString"      '"$INSTDIR\uninstall.exe"'
    WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
    WriteRegDWORD HKLM "${UNINST_KEY}" "NoModify" 1
    WriteRegDWORD HKLM "${UNINST_KEY}" "NoRepair" 1
    ; Add/Remove Programs reads this as a DWORD of kilobytes, which is exactly
    ; what /S=0K returns.
    ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
    IntFmt $0 "0x%08X" $0
    WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" $0
SectionEnd

Section "Start Menu shortcut" SecStartMenu
    ; One shortcut, no program folder: a single-application folder is noise in
    ; the Windows 10/11 Start menu, and the uninstaller lives in Add/Remove
    ; Programs rather than next to it.
    ; --toggle rather than a bare launch: it raises the running window
    ; instead of a second copy refusing to start on the held index lock.
    CreateShortcut "$SMPROGRAMS\${APP}.lnk" "$INSTDIR\quicksearch.exe" "--toggle" "$INSTDIR\quicksearch.ico"
SectionEnd

Section "Search hotkey (Ctrl+Shift+F)" SecHotkey
    ; The .lnk "shortcut key" field is the only thing on Windows that binds a
    ; key to a command, and it is what makes the shortcut work while
    ; QuickSearch is closed - nothing an application registers for itself can
    ; fire when it is not running. Windows only honours the field on a
    ; shortcut in the Start menu or on the desktop. Modifier combinations
    ; like Ctrl+Shift are accepted as-is; only a bare key gets Ctrl+Alt added
    ; for it. Ctrl+Shift+F matches the in-application default, which answers
    ; the key instantly while the window is open; this .lnk binding is the
    ; slower launch path Explorer takes when it is not. Changing the shortcut
    ; on the Settings tab rewrites this .lnk to match (per-user installs
    ; only; this all-users file needs elevation).
    ;
    ; Explorer registers this key at logon, so the app's own RegisterHotKey
    ; for the same combination loses; either way the press works — directly,
    ; or by Explorer launching `--toggle`, which relays to a running window.
    ;
    ; Rewrites the same shortcut the section above creates: NSIS cannot add a
    ; hotkey to an existing .lnk, and creating it twice is harmless.
    CreateShortcut "$SMPROGRAMS\${APP}.lnk" "$INSTDIR\quicksearch.exe" "--toggle" \
        "$INSTDIR\quicksearch.ico" 0 SW_SHOWNORMAL CONTROL|SHIFT|F \
        "Search your files with ${APP}"
SectionEnd

Section /o "Desktop shortcut" SecDesktop
    ; Deliberately no hotkey here: two shortcuts claiming one key is a
    ; conflict, and the Start menu entry above already owns it.
    CreateShortcut "$DESKTOP\${APP}.lnk" "$INSTDIR\quicksearch.exe" "--toggle" "$INSTDIR\quicksearch.ico"
SectionEnd

; There is deliberately no "add to PATH" section, tempting as one is for
; quicksearch-cli. Editing PATH means reading the machine value, appending and
; writing it back, and NSIS strings in the standard build are capped at
; NSIS_MAX_STRLEN (1024 characters, `makensis -HDRINFO`). ReadRegStr truncates
; silently at that cap, so on any machine with a long PATH the write-back would
; destroy the rest of it - a spectacular failure for an optional checkbox. The
; README tells people to add the directory themselves.

!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
    !insertmacro MUI_DESCRIPTION_TEXT ${SecApp} \
        "The desktop app and quicksearch-cli, the terminal search tool."
    !insertmacro MUI_DESCRIPTION_TEXT ${SecStartMenu} \
        "Add ${APP} to the Start menu for all users."
    !insertmacro MUI_DESCRIPTION_TEXT ${SecHotkey} \
        "Press Ctrl+Shift+F anywhere to search, starting ${APP} if it is not \
         already running. The Settings tab can rebind it."
    !insertmacro MUI_DESCRIPTION_TEXT ${SecDesktop} \
        "Add a ${APP} shortcut to the desktop."
!insertmacro MUI_FUNCTION_DESCRIPTION_END

; The installer is elevated and anything it starts inherits that. Handing the
; path to the already-running Explorer starts the app as the logged-on user
; instead, so a first run creates its config and index in the right profile.
Function LaunchAsUser
    Exec '"$WINDIR\explorer.exe" "$INSTDIR\quicksearch.exe"'
FunctionEnd

; ----------------------------------------------------------- uninstaller ----

Section "Uninstall"
    ; Same running-process test as the install side. Stopping here leaves the
    ; installation intact rather than gutted.
    ClearErrors
    Delete "$INSTDIR\quicksearch.exe"
    Delete "$INSTDIR\quicksearch-cli.exe"
    ${If} ${Errors}
        MessageBox MB_OK|MB_ICONSTOP \
            "${APP} is still running. Close it and try again." /SD IDOK
        Abort
    ${EndIf}

    Delete "$INSTDIR\quicksearch.ico"
    Delete "$INSTDIR\README.md"
    Delete "$INSTDIR\LICENSE.txt"
    Delete "$INSTDIR\config_example.toml"
    Delete "$INSTDIR\uninstall.exe"
    Delete "$SMPROGRAMS\${APP}.lnk"
    Delete "$DESKTOP\${APP}.lnk"
    ; Plain RMDir, never /r: a portable-mode config.toml and the index beside it
    ; may be sitting in this directory, and neither is ours to delete. A
    ; directory holding anything the installer did not put there simply stays.
    RMDir "$INSTDIR"

    DeleteRegKey HKLM "${UNINST_KEY}"
    ; The per-user config in %APPDATA%\quicksearch and the index in
    ; %LOCALAPPDATA%\quicksearch are left alone, the way apt leaves ~/.config
    ; alone: reinstalling picks up where the last install left off, and nobody
    ; loses an index to an upgrade.
SectionEnd
