@echo off
REM ============================================================================
REM install-claude.cmd - Interactive Claude Code installer for Windows 11
REM
REM Menu: install for current user, administrator, or both.
REM   - User install  : runs inline if non-elevated, else de-elevates via
REM                     Shell.Application COM (installs to right profile)
REM   - Admin install : runs inline if elevated, else elevates via
REM                     Start-Process -Verb RunAs
REM   - Both          : runs user first (waits), then admin (waits)
REM
REM After install, persists %USERPROFILE%\.local\bin to the user's PATH via
REM [Environment]::SetEnvironmentVariable, verifies the write, and prints a
REM loud error + manual fix if anything fails.
REM
REM Implementation note: we use !VAR! (delayed expansion) inside all if(...)
REM blocks because %VAR% is expanded at parse time, and if the value contains
REM parens (e.g. PATH containing "C:\Program Files (x86)\..."), cmd's block
REM parser gets confused and throws "unexpected at this time" errors.
REM
REM Flags:
REM   /force        reinstall even if present
REM   /npm          use npm instead of native installer (needs Node.js 18+)
REM   /skipgit      skip Git for Windows check
REM   /user         preselect User (skip menu)
REM   /admin        preselect Admin (skip menu)
REM   /both         preselect Both (skip menu)
REM
REM Internal flags (set automatically for spawned children):
REM   /install-now     do the install immediately (skip menu)
REM   /marker <path>   write 'ok' or 'fail' to this file on completion
REM ============================================================================

setlocal EnableDelayedExpansion

set "FORCE=0"
set "USE_NPM=0"
set "SKIP_GIT=0"
set "INSTALL_NOW=0"
set "MARKER="
set "PRESELECT="

:parse
if "%~1"=="" goto parsed
if /i "%~1"=="/force"       ( set "FORCE=1"       & shift & goto parse )
if /i "%~1"=="/npm"         ( set "USE_NPM=1"     & shift & goto parse )
if /i "%~1"=="/skipgit"     ( set "SKIP_GIT=1"    & shift & goto parse )
if /i "%~1"=="/install-now" ( set "INSTALL_NOW=1" & shift & goto parse )
if /i "%~1"=="/marker"      ( set "MARKER=%~2"    & shift & shift & goto parse )
if /i "%~1"=="/user"        ( set "PRESELECT=user"  & shift & goto parse )
if /i "%~1"=="/admin"       ( set "PRESELECT=admin" & shift & goto parse )
if /i "%~1"=="/both"        ( set "PRESELECT=both"  & shift & goto parse )
if /i "%~1"=="/?"           goto usage
if /i "%~1"=="-h"           goto usage
echo Unknown argument: %~1
goto usage
:parsed

REM -- Detect elevation --
set "IS_ADMIN=0"
net session >nul 2>&1
if !errorlevel! equ 0 set "IS_ADMIN=1"

REM -- If spawned as a child to do the install, skip menu --
if "!INSTALL_NOW!"=="1" goto install_now_mode

REM -- Preselect via flag, else show menu --
if defined PRESELECT (
    set "CHOICE=!PRESELECT!"
    goto dispatch
)

:menu
cls
echo.
echo  =============================================
echo   Claude Code Installer
echo  =============================================
echo   User:      %USERNAME%
if "!IS_ADMIN!"=="1" (
    echo   Context:   Running elevated ^(administrator^)
) else (
    echo   Context:   Running as standard user
)
echo.
echo   Install Claude Code for:
echo.
echo     [1] Current user only
echo     [2] Administrator only
echo     [3] Both
echo     [Q] Cancel
echo.
set "CHOICE="
set /p "SEL=Your choice: "
if /i "!SEL!"=="1" set "CHOICE=user"
if /i "!SEL!"=="2" set "CHOICE=admin"
if /i "!SEL!"=="3" set "CHOICE=both"
if /i "!SEL!"=="q" goto cancel
if not defined CHOICE (
    echo Invalid choice. Enter 1, 2, 3, or Q.
    timeout /t 2 /nobreak >nul
    goto menu
)

:dispatch
if "!CHOICE!"=="user"  ( call :do_user_install  & goto finish )
if "!CHOICE!"=="admin" ( call :do_admin_install & goto finish )
if "!CHOICE!"=="both" (
    call :do_user_install
    if !errorlevel! equ 0 (
        call :do_admin_install
    ) else (
        echo.
        echo [*] User install did not succeed; skipping admin install.
    )
    goto finish
)

:finish
echo.
echo Open a new terminal and run:  claude
pause
endlocal
exit /b 0

:cancel
echo Cancelled.
endlocal
exit /b 0

:usage
echo.
echo Usage: install-claude.cmd [flags]
echo.
echo   /force     reinstall even if present
echo   /npm       use npm instead of native installer ^(needs Node.js 18+^)
echo   /skipgit   skip Git for Windows check
echo   /user      install for current user ^(skip menu^)
echo   /admin     install for administrator ^(skip menu^)
echo   /both      install for both ^(skip menu^)
echo.
endlocal
exit /b 2

REM ============================================================================
REM Install-now: we were spawned as a child to do the real work
REM ============================================================================
:install_now_mode
call :do_install_inline
set "STATUS=fail"
if !errorlevel! equ 0 set "STATUS=ok"
if defined MARKER (
    echo !STATUS!>"!MARKER!"
)
echo.
echo [+] Finished. Press any key to close this window.
pause >nul
endlocal
if "!STATUS!"=="ok" (exit /b 0) else (exit /b 1)

REM ============================================================================
REM User install: inline if non-elevated, else de-elevate via Shell.Application
REM ============================================================================
:do_user_install
echo.
echo --- Installing for current user ---
if "!IS_ADMIN!"=="1" (
    call :spawn_user_child
) else (
    call :do_install_inline
)
exit /b !errorlevel!

:spawn_user_child
for %%I in ("%TEMP%") do set "STEMP=%%~sI"
set "MKR=!STEMP!\cinst%RANDOM%%RANDOM%.tmp"
if exist "!MKR!" del "!MKR!"

set "CARGS=/install-now /marker !MKR!"
if "!FORCE!"=="1"    set "CARGS=!CARGS! /force"
if "!USE_NPM!"=="1"  set "CARGS=!CARGS! /npm"
if "!SKIP_GIT!"=="1" set "CARGS=!CARGS! /skipgit"

echo [+] Launching non-elevated installer in a new window...
powershell -NoProfile -ExecutionPolicy Bypass -Command "(New-Object -ComObject Shell.Application).ShellExecute('%~f0', '!CARGS!', '', '', 1)"
if !errorlevel! neq 0 (
    echo [x] Failed to launch non-elevated process.
    exit /b 1
)

echo [+] Waiting for completion ^(timeout 10 min^)...
set /a W=0
:user_wait
if exist "!MKR!" goto user_wait_done
timeout /t 2 /nobreak >nul
set /a W+=1
if !W! gtr 300 (
    echo [x] Timed out waiting for user install.
    exit /b 1
)
goto user_wait

:user_wait_done
set "USTATUS="
set /p USTATUS=<"!MKR!"
del "!MKR!" 2>nul
if "!USTATUS!"=="ok" (
    echo [+] User install: OK
    exit /b 0
)
echo [x] User install: FAILED
exit /b 1

REM ============================================================================
REM Admin install: inline if elevated, else elevate via Start-Process -Verb RunAs
REM ============================================================================
:do_admin_install
echo.
echo --- Installing for administrator ---
if "!IS_ADMIN!"=="1" (
    call :do_install_inline
) else (
    call :spawn_admin_child
)
exit /b !errorlevel!

:spawn_admin_child
set "CARGS=/install-now"
if "!FORCE!"=="1"    set "CARGS=!CARGS! /force"
if "!USE_NPM!"=="1"  set "CARGS=!CARGS! /npm"
if "!SKIP_GIT!"=="1" set "CARGS=!CARGS! /skipgit"

echo [+] Launching elevated installer ^(UAC prompt expected^)...
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Start-Process -FilePath '%~f0' -ArgumentList '!CARGS!' -Verb RunAs -Wait -ErrorAction Stop; exit 0 } catch { Write-Host $_.Exception.Message; exit 1 }"
if !errorlevel! neq 0 (
    echo [x] Elevated install was cancelled or failed.
    exit /b 1
)
echo [+] Admin install: OK
exit /b 0

REM ============================================================================
REM Core install logic (runs in whatever context the caller is in)
REM ============================================================================
:do_install_inline
echo     Current USERPROFILE: %USERPROFILE%
where claude >nul 2>&1
if !errorlevel! equ 0 if "!FORCE!"=="0" (
    echo [+] Claude Code already installed:
    claude --version
    echo     Use /force to reinstall.
    call :add_to_user_path
    exit /b 0
)

if "!SKIP_GIT!"=="1" goto inline_git_skip
if "!USE_NPM!"=="1"  goto inline_git_skip
where git >nul 2>&1
if !errorlevel! neq 0 (
    echo [*] Git for Windows not found.
    where winget >nul 2>&1
    if !errorlevel! neq 0 (
        echo [x] winget not available. Install Git from https://git-scm.com and re-run.
        exit /b 1
    )
    echo [+] Installing Git.Git via winget ^(winget source only, no msstore^)...
    winget install --id Git.Git -e --source winget --accept-package-agreements --accept-source-agreements
    if !errorlevel! neq 0 (
        echo [x] Git install failed.
        exit /b 1
    )
) else (
    echo [+] Git for Windows found.
)
:inline_git_skip

if "!USE_NPM!"=="1" (
    where npm >nul 2>&1
    if !errorlevel! neq 0 (
        echo [x] npm not found. Install Node.js 18+ first.
        exit /b 1
    )
    echo [+] Installing via npm...
    call npm install -g @anthropic-ai/claude-code
    if !errorlevel! neq 0 exit /b 1
) else (
    echo [+] Running native installer ^(PowerShell ExecutionPolicy Bypass for this call only^)...
    powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"
    if !errorlevel! neq 0 (
        echo [x] Native installer failed.
        exit /b 1
    )
)

REM Make this session aware of .local\bin so post-install 'where claude' works
call :add_session_path

REM Persist to user PATH so new terminals find claude automatically
call :add_to_user_path

REM Verify install
call :verify_install
exit /b 0

REM ============================================================================
REM Append .local\bin to the current session's PATH if not already present.
REM Uses !PATH! (delayed expansion) so parens in PATH (e.g. "Program Files
REM (x86)") don't confuse cmd's parser.
REM ============================================================================
:add_session_path
echo ;!PATH!; | find /i ";%USERPROFILE%\.local\bin;" >nul
if errorlevel 1 set "PATH=!PATH!;%USERPROFILE%\.local\bin"
exit /b 0

REM ============================================================================
REM Verify install — kept as its own subroutine to avoid parens inside blocks
REM ============================================================================
:verify_install
where claude >nul 2>&1
if !errorlevel! equ 0 goto verify_found_on_path
if exist "%USERPROFILE%\.local\bin\claude.exe" goto verify_found_at_path
echo [*] claude.exe not found at expected location.
exit /b 0
:verify_found_on_path
echo [+] Installed:
claude --version
for /f "delims=" %%i in ('where claude') do echo     Location: %%i
exit /b 0
:verify_found_at_path
echo [+] Installed: %USERPROFILE%\.local\bin\claude.exe
"%USERPROFILE%\.local\bin\claude.exe" --version
exit /b 0

REM ============================================================================
REM Persist %USERPROFILE%\.local\bin to the user's PATH.
REM Single-line PowerShell, verifies write via registry read-back, prints
REM a loud error + manual-fix command if anything fails.
REM ============================================================================
:add_to_user_path
echo [+] Persisting %USERPROFILE%\.local\bin to your user PATH...
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $t = Join-Path $env:USERPROFILE '.local\bin'; $p = [Environment]::GetEnvironmentVariable('Path','User'); if ([string]::IsNullOrEmpty($p)) { [Environment]::SetEnvironmentVariable('Path', $t, 'User'); Write-Host ('    Set user PATH to: ' + $t) } elseif ((';' + $p.Trim(';') + ';').ToLower().Contains((';' + $t + ';').ToLower())) { Write-Host ('    Already in user PATH: ' + $t) } else { $newPath = $p.TrimEnd(';') + ';' + $t; [Environment]::SetEnvironmentVariable('Path', $newPath, 'User'); Write-Host ('    Appended to user PATH: ' + $t) }; $v = [Environment]::GetEnvironmentVariable('Path','User'); if ($v -and $v.ToLower().Contains($t.ToLower())) { Write-Host '    Registry verify: OK'; exit 0 } else { Write-Host '    Registry verify: FAILED'; exit 2 } } catch { Write-Host ('    ERROR: ' + $_.Exception.Message); exit 3 }"
set "PSEXIT=!errorlevel!"
if !PSEXIT! neq 0 call :path_error !PSEXIT!
exit /b 0

REM ============================================================================
REM PATH-persistence error message (lives outside any if-block so the
REM powershell command with its many parens doesn't get mis-parsed).
REM ============================================================================
:path_error
echo.
echo [x] PATH persistence failed ^(powershell exit %~1^).
echo     Run this manually in a cmd window to fix:
echo.
echo     powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('Path', ([Environment]::GetEnvironmentVariable('Path','User').TrimEnd(';') + ';' + $env:USERPROFILE + '\.local\bin'), 'User')"
echo.
echo     Then open a new cmd and run:  claude --version
echo.
exit /b 0
