; ============================================================================
; GE Aerospace Network Drive Manager - Full Inno Setup Implementation
; All PowerShell logic integrated directly into Inno Setup
; Version 2.0
; ============================================================================

[Setup]
AppId={{E3F4A5B6-C7D8-9E0F-1A2B-3C4D5E6F7890}}
AppName=GE Aerospace Network Drive Manager
AppVersion=2.0
AppPublisher=WJDT
AppPublisherURL=http://tsgwp00525.rd.ds.ge.com
DefaultDirName={tmp}\NetworkDriveManager
CreateAppDir=no
PrivilegesRequired=lowest
OutputDir=Output
OutputBaseFilename=NetworkDriveManager
SolidCompression=yes
WizardStyle=modern
SetupIconFile=gea-logo.ico
WizardImageFile=patrick.bmp
WizardSmallImageFile=patrick-sm.bmp
DisableWelcomePage=yes
DisableDirPage=yes
DisableProgramGroupPage=yes
DisableReadyPage=yes
DisableFinishedPage=yes
Uninstallable=no

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Code]
// ============================================================================
// CONSTANTS AND CONFIGURATION
// ============================================================================

const
  // Legacy domain servers to manage
  // OLD_SERVER is detected in existing mappings, NEW_SERVER is used when remapping
  OLD_SERVER_1 = 'tsgwp00525.rd.ds.ge.com';
  NEW_SERVER_1 = 'tsgwp00525.wjs.geaerospace.net';
  // Add more servers as needed:
  // OLD_SERVER_2 = 'server2.rd.ds.ge.com';
  // NEW_SERVER_2 = 'server2.newdomain.com';

  LEGACY_DOMAIN = 'logon.ds.ge.com';
  PASSWORD_RESET_URL = 'https://mypassword.ge.com';
  BACKUP_FILENAME = 'NetworkDriveMappings.json';

  // Menu options
  MENU_VIEW_MAPPINGS = 1;
  MENU_BACKUP = 2;
  MENU_TEST_CREDS = 3;
  MENU_FULL_RESET = 4;
  MENU_RESTORE = 5;
  MENU_ADD_DRIVES = 6;
  MENU_PASSWORD_PORTAL = 7;
  MENU_QUIT = 8;

  // Predefined drive mappings
  DRIVE_S_PATH = '\\tsgwp00525.wjs.geaerospace.net\shared';
  DRIVE_S_DESC = 'S: - Shared Drive (Standard)';

  DRIVE_V_PATH = '\\tsgwp00525.wjs.geaerospace.net\eng_apps';
  DRIVE_V_DESC = 'V: - Engineering Apps (Engineers)';

  DRIVE_Y_SERVER = 'win.cighpeifep036.logon.ds.ge.com';
  DRIVE_Y_PATH = '\\win.cighpeifep036.logon.ds.ge.com\ugcam_res_lib';
  DRIVE_Y_DESC = 'Y: - UGCAM Resource Library (Engineers)';

  // Newline constant for script building
  NL = #13#10;

// ============================================================================
// GLOBAL VARIABLES
// ============================================================================

var
  MainMenuPage: TWizardPage;
  PasswordPage: TInputQueryWizardPage;
  ProgressPage: TOutputProgressWizardPage;
  ResultsPage: TOutputMsgMemoWizardPage;

  MenuListBox: TNewListBox;
  StatusLabel: TLabel;
  MappingsListBox: TNewListBox;

  CurrentPassword: String;
  LegacyUsername: String;
  BackupFilePath: String;

  SelectedMenuOption: Integer;

// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================

function GetLegacyUsername: String;
begin
  Result := GetUserNameString + '@' + LEGACY_DOMAIN;
end;

function GetBackupFilePath: String;
begin
  Result := ExpandConstant('{userdocs}\') + BACKUP_FILENAME;
end;

function GetLegacyServers: TArrayOfString;
begin
  // Return NEW server for credential testing (that's what we'll connect to)
  SetArrayLength(Result, 1);
  Result[0] := NEW_SERVER_1;
end;

// Execute a command and capture output to a file
function ExecWithOutput(const Cmd, Params, WorkDir: String; var Output: String): Integer;
var
  TempFile: String;
  ResultCode: Integer;
  OutputAnsi: AnsiString;
begin
  TempFile := ExpandConstant('{tmp}\cmd_output_') + IntToStr(Random(99999)) + '.txt';

  Exec('cmd.exe', '/c ' + Cmd + ' ' + Params + ' > "' + TempFile + '" 2>&1',
       WorkDir, SW_HIDE, ewWaitUntilTerminated, ResultCode);

  if FileExists(TempFile) then
  begin
    if LoadStringFromFile(TempFile, OutputAnsi) then
      Output := String(OutputAnsi)
    else
      Output := '';
    DeleteFile(TempFile);
  end
  else
    Output := '';

  Result := ResultCode;
end;

// Execute PowerShell command and capture output
function ExecPowerShell(const Script: String; var Output: String): Integer;
var
  TempScript, TempOutput: String;
  ResultCode: Integer;
  OutputAnsi: AnsiString;
  CmdLine: String;
begin
  TempScript := ExpandConstant('{tmp}\ps_script_') + IntToStr(Random(99999)) + '.ps1';
  TempOutput := ExpandConstant('{tmp}\ps_output_') + IntToStr(Random(99999)) + '.txt';

  // Save script to temp file
  if not SaveStringToFile(TempScript, Script, False) then
  begin
    Output := 'ERROR: Could not save script to temp file';
    Result := -1;
    Exit;
  end;

  // Build command line - must use cmd.exe for redirection to work
  CmdLine := '/c powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' + TempScript + '" > "' + TempOutput + '" 2>&1';

  // Execute via cmd.exe
  if not Exec('cmd.exe', CmdLine, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
  begin
    Output := 'ERROR: Could not execute PowerShell';
    Result := -1;
    DeleteFile(TempScript);
    Exit;
  end;

  // Read output
  if FileExists(TempOutput) then
  begin
    if LoadStringFromFile(TempOutput, OutputAnsi) then
      Output := String(OutputAnsi)
    else
      Output := 'ERROR: Could not read output file';
    DeleteFile(TempOutput);
  end
  else
    Output := 'ERROR: No output file created';

  DeleteFile(TempScript);
  Result := ResultCode;
end;

// Parse error code from net use output
function ParseNetUseErrorCode(const Output: String; ExitCode: Integer): Integer;
var
  ErrorPos: Integer;
  ErrorStr: String;
  I: Integer;
begin
  Result := ExitCode;

  // Look for "System error XXXX"
  ErrorPos := Pos('System error ', Output);
  if ErrorPos > 0 then
  begin
    ErrorStr := '';
    for I := ErrorPos + 13 to Length(Output) do
    begin
      if (Output[I] >= '0') and (Output[I] <= '9') then
        ErrorStr := ErrorStr + Output[I]
      else
        Break;
    end;
    if ErrorStr <> '' then
      Result := StrToIntDef(ErrorStr, ExitCode);
  end
  else
  begin
    // Look for "error XXXX"
    ErrorPos := Pos('error ', Output);
    if ErrorPos > 0 then
    begin
      ErrorStr := '';
      for I := ErrorPos + 6 to Length(Output) do
      begin
        if (Output[I] >= '0') and (Output[I] <= '9') then
          ErrorStr := ErrorStr + Output[I]
        else
          Break;
      end;
      if ErrorStr <> '' then
        Result := StrToIntDef(ErrorStr, ExitCode);
    end;
  end;
end;

// Get error message for error code
function GetErrorMessage(ErrorCode: Integer): String;
begin
  case ErrorCode of
    0: Result := 'Success';
    5: Result := 'Access denied - you may not have permission to this share';
    53: Result := 'Network path not found - Zscaler may need re-authentication';
    64: Result := 'Network connection was dropped unexpectedly';
    67: Result := 'Network name cannot be found - invalid share path';
    85: Result := 'Drive letter is already in use';
    86: Result := 'Invalid password';
    1219: Result := 'Multiple connections to server exist with different credentials';
    1326: Result := 'Logon failure - unknown username or bad password';
    1327: Result := 'Account restriction - policy is preventing logon';
    1330: Result := 'Password has expired';
    1331: Result := 'Account is disabled';
    1909: Result := 'Account is locked out';
  else
    Result := 'Unknown error (code: ' + IntToStr(ErrorCode) + ')';
  end;
end;

// Get action for error code
function GetErrorAction(ErrorCode: Integer): String;
begin
  case ErrorCode of
    5: Result := 'contact_manager';
    53: Result := 'zscaler_reauth';
    64: Result := 'retry_or_zscaler';
    67: Result := 'verify_path';
    85: Result := 'reassign_drive';
    86, 1326, 1330, 1909: Result := 'reset_password';
    1219: Result := 'clear_connections';
    1327, 1331: Result := 'contact_helpdesk';
  else
    Result := 'unknown';
  end;
end;

// ============================================================================
// DRIVE MAPPING FUNCTIONS
// ============================================================================

function GetCurrentDriveMappings(LegacyOnly: Boolean): String;
var
  Script: String;
  Output: String;
begin
  Script :=
    '# Detect old, new, and Y: drive server names' + NL +
    '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL +
    '$mappings = @()' + NL +
    '$netDrives = Get-WmiObject -Class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue' + NL +
    'foreach ($drive in $netDrives) {' + NL +
    '    $isLegacy = $false' + NL +
    '    foreach ($server in $LegacyServers) {' + NL +
    '        if ($drive.ProviderName -like "*$server*") { $isLegacy = $true; break }' + NL +
    '    }' + NL;

  if LegacyOnly then
    Script := Script + '    if ($isLegacy) {' + NL
  else
    Script := Script + '    if ($true) {' + NL;

  Script := Script +
    '        $tag = if ($isLegacy) { " [LEGACY]" } else { "" }' + NL +
    '        $mappings += "$($drive.DeviceID) -> $($drive.ProviderName)$tag"' + NL +
    '    }' + NL +
    '}' + NL +
    'if ($mappings.Count -eq 0) { Write-Output "No network drives mapped." }' + NL +
    'else { $mappings | ForEach-Object { Write-Output $_ } }';

  ExecPowerShell(Script, Output);
  Result := Output;
end;

function GetDriveMappingsAsJSON: String;
var
  Script: String;
  Output: String;
begin
  Script :=
    '# Detect old, new, and Y: drive server names' + NL +
    '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL +
    '$mappings = @()' + NL +
    '$netDrives = Get-WmiObject -Class Win32_MappedLogicalDisk -ErrorAction SilentlyContinue' + NL +
    'foreach ($drive in $netDrives) {' + NL +
    '    $isLegacy = $false' + NL +
    '    foreach ($server in $LegacyServers) {' + NL +
    '        if ($drive.ProviderName -like "*$server*") { $isLegacy = $true; break }' + NL +
    '    }' + NL +
    '    $serverName = if ($drive.ProviderName -match "\\\\([^\\]+)\\") { $Matches[1] } else { "" }' + NL +
    '    $shareName = if ($drive.ProviderName -match "\\\\[^\\]+\\(.+)$") { $Matches[1] } else { "" }' + NL +
    '    $mappings += [PSCustomObject]@{' + NL +
    '        DriveLetter = $drive.DeviceID' + NL +
    '        RemotePath = $drive.ProviderName' + NL +
    '        ServerName = $serverName' + NL +
    '        ShareName = $shareName' + NL +
    '        IsLegacy = $isLegacy' + NL +
    '    }' + NL +
    '}' + NL +
    '$backup = [PSCustomObject]@{' + NL +
    '    BackupDate = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")' + NL +
    '    ComputerName = $env:COMPUTERNAME' + NL +
    '    Username = $env:USERNAME' + NL +
    '    Mappings = $mappings' + NL +
    '}' + NL +
    '$backup | ConvertTo-Json -Depth 3';

  ExecPowerShell(Script, Output);
  Result := Output;
end;

// ============================================================================
// BACKUP/RESTORE FUNCTIONS
// ============================================================================

function BackupDriveMappings: Boolean;
var
  JSONContent: String;
begin
  Result := False;
  JSONContent := GetDriveMappingsAsJSON;

  if JSONContent <> '' then
  begin
    Result := SaveStringToFile(BackupFilePath, JSONContent, False);
  end;
end;

function LoadBackupInfo(var BackupDate, MappingsList: String): Boolean;
var
  Script: String;
  Output: String;
  JSONContent: AnsiString;
begin
  Result := False;
  BackupDate := '';
  MappingsList := '';

  if not FileExists(BackupFilePath) then
    Exit;

  if not LoadStringFromFile(BackupFilePath, JSONContent) then
    Exit;

  Script :=
    '$json = Get-Content -Path "' + BackupFilePath + '" -Raw | ConvertFrom-Json' + NL +
    'Write-Output "DATE:$($json.BackupDate)"' + NL +
    'foreach ($m in $json.Mappings) {' + NL +
    '    $tag = if ($m.IsLegacy) { " [LEGACY]" } else { "" }' + NL +
    '    Write-Output "MAP:$($m.DriveLetter) -> $($m.RemotePath)$tag"' + NL +
    '}';

  if ExecPowerShell(Script, Output) = 0 then
  begin
    // Parse output
    if Pos('DATE:', Output) > 0 then
    begin
      BackupDate := Copy(Output, Pos('DATE:', Output) + 5, 19);
      MappingsList := Output;
      Result := True;
    end;
  end;
end;

// ============================================================================
// CREDENTIAL TESTING
// ============================================================================

function TestCredentials(const Server, Username, Password: String; var ErrorCode: Integer; var ErrorMsg: String): Boolean;
var
  Output: String;
  ExitCode: Integer;
  TestPath: String;
begin
  Result := False;
  TestPath := '\\' + Server + '\IPC$';

  // First clear any existing connections
  ExecWithOutput('net', 'use "' + TestPath + '" /delete /y', '', Output);
  ExecWithOutput('net', 'use "\\' + Server + '" /delete /y', '', Output);

  // Try to connect
  ExitCode := ExecWithOutput('net', 'use "' + TestPath + '" /user:' + Username + ' "' + Password + '"', '', Output);

  // Parse actual error code from output
  ErrorCode := ParseNetUseErrorCode(Output, ExitCode);

  // Clean up test connection
  ExecWithOutput('net', 'use "' + TestPath + '" /delete /y', '', Output);

  if ErrorCode = 0 then
  begin
    Result := True;
    ErrorMsg := 'Credentials validated successfully';
  end
  else
  begin
    ErrorMsg := GetErrorMessage(ErrorCode);
  end;
end;

// ============================================================================
// CONNECTION MANAGEMENT
// ============================================================================

function ClearAllLegacyConnections: String;
var
  Script: String;
  Output: String;
begin
  Script :=
    '# Include old, new, and Y: drive server names' + NL +
    '$LegacyServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL +
    '$results = @()' + NL +
    NL +
    '# Clear IPC$ and server connections' + NL +
    'foreach ($server in $LegacyServers) {' + NL +
    '    $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL +
    '    $null = net use "\\$server" /delete /y 2>&1' + NL +
    '}' + NL +
    NL +
    '# Clear mapped drives' + NL +
    '$netUseList = net use 2>&1' + NL +
    'foreach ($server in $LegacyServers) {' + NL +
    '    $connections = $netUseList | Select-String -Pattern $server' + NL +
    '    foreach ($conn in $connections) {' + NL +
    '        if ($conn -match "^(OK|Disconnected|Unavailable)?\s*([A-Z]:)") {' + NL +
    '            $drive = $Matches[2]' + NL +
    '            $null = net use $drive /delete /y 2>&1' + NL +
    '            $results += "Disconnected $drive"' + NL +
    '        }' + NL +
    '    }' + NL +
    '}' + NL +
    NL +
    '# Clear cached credentials' + NL +
    'foreach ($server in $LegacyServers) {' + NL +
    '    $null = cmdkey /delete:$server 2>&1' + NL +
    '    $null = cmdkey /delete:"Domain:target=$server" 2>&1' + NL +
    '    $results += "Cleared credentials for $server"' + NL +
    '}' + NL +
    NL +
    'if ($results.Count -eq 0) { Write-Output "No connections to clear" }' + NL +
    'else { $results | ForEach-Object { Write-Output $_ } }';

  ExecPowerShell(Script, Output);
  Result := Output;
end;

function ReconnectDrivesFromBackup(const Username, Password: String): String;
var
  Script: String;
  Output: String;
begin
  Script :=
    '$Username = "' + Username + '"' + NL +
    '$Password = "' + Password + '"' + NL +
    '$BackupFile = "' + BackupFilePath + '"' + NL +
    NL +
    '# Server name mapping (old -> new)' + NL +
    '$ServerMapping = @{' + NL +
    '    "' + OLD_SERVER_1 + '" = "' + NEW_SERVER_1 + '"' + NL +
    '}' + NL +
    NL +
    '# All managed servers' + NL +
    '$ManagedServers = @("' + OLD_SERVER_1 + '", "' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL +
    NL +
    'if (-not (Test-Path $BackupFile)) {' + NL +
    '    Write-Output "ERROR: No backup file found"' + NL +
    '    exit 1' + NL +
    '}' + NL +
    NL +
    '$backup = Get-Content $BackupFile -Raw | ConvertFrom-Json' + NL +
    '$results = @()' + NL +
    NL +
    '# PHASE 1: Collect all legacy mappings and prepare new paths' + NL +
    '$drivesToMap = @()' + NL +
    'foreach ($mapping in $backup.Mappings) {' + NL +
    '    if ($mapping.IsLegacy) {' + NL +
    '        $drive = $mapping.DriveLetter' + NL +
    '        $path = $mapping.RemotePath' + NL +
    '        $originalPath = $path' + NL +
    NL +
    '        # Replace old server names with new ones' + NL +
    '        foreach ($oldServer in $ServerMapping.Keys) {' + NL +
    '            $newServer = $ServerMapping[$oldServer]' + NL +
    '            if ($path -like "*$oldServer*") {' + NL +
    '                $path = $path -replace [regex]::Escape($oldServer), $newServer' + NL +
    '                $results += "MIGRATING: $originalPath -> $path"' + NL +
    '            }' + NL +
    '        }' + NL +
    NL +
    '        $drivesToMap += [PSCustomObject]@{' + NL +
    '            DriveLetter = $drive' + NL +
    '            Path = $path' + NL +
    '        }' + NL +
    '    }' + NL +
    '}' + NL +
    NL +
    'if ($drivesToMap.Count -eq 0) {' + NL +
    '    Write-Output "No legacy drives to reconnect"' + NL +
    '    exit 0' + NL +
    '}' + NL +
    NL +
    '# PHASE 2: Disconnect ALL drives to managed servers FIRST' + NL +
    '# This prevents error 1219 when multiple drives point to same server' + NL +
    '$results += ""' + NL +
    '$results += "Disconnecting existing drives..."' + NL +
    'foreach ($driveInfo in $drivesToMap) {' + NL +
    '    $null = net use $driveInfo.DriveLetter /delete /y 2>&1' + NL +
    '}' + NL +
    NL +
    '# Also clear IPC$ connections to prevent 1219' + NL +
    'foreach ($server in $ManagedServers) {' + NL +
    '    $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL +
    '    $null = net use "\\$server" /delete /y 2>&1' + NL +
    '}' + NL +
    NL +
    '# Clear cached credentials' + NL +
    'foreach ($server in $ManagedServers) {' + NL +
    '    $null = cmdkey /delete:$server 2>&1' + NL +
    '}' + NL +
    NL +
    '# Small delay to let Windows release connections' + NL +
    'Start-Sleep -Seconds 2' + NL +
    NL +
    '# PHASE 3: Reconnect ALL drives' + NL +
    '$results += ""' + NL +
    '$results += "Reconnecting drives..."' + NL +
    'foreach ($driveInfo in $drivesToMap) {' + NL +
    '    $drive = $driveInfo.DriveLetter' + NL +
    '    $path = $driveInfo.Path' + NL +
    NL +
    '    $output = net use $drive $path /user:$Username $Password /persistent:yes 2>&1' + NL +
    '    $exitCode = $LASTEXITCODE' + NL +
    NL +
    '    if ($exitCode -eq 0) {' + NL +
    '        $results += "OK: $drive -> $path"' + NL +
    '    } else {' + NL +
    '        $errCode = $exitCode' + NL +
    '        $outStr = $output | Out-String' + NL +
    '        if ($outStr -match "System error (\d+)") { $errCode = [int]$Matches[1] }' + NL +
    '        $results += "FAILED: $drive -> $path (Error $errCode)"' + NL +
    '    }' + NL +
    '}' + NL +
    NL +
    '$results | ForEach-Object { Write-Output $_ }';

  ExecPowerShell(Script, Output);
  Result := Output;
end;

// ============================================================================
// UI HELPER FUNCTIONS
// ============================================================================

procedure UpdateStatusLabel(const Msg: String);
begin
  if StatusLabel <> nil then
    StatusLabel.Caption := Msg;
end;

procedure ShowResultsInMemo(const Title, Content: String);
var
  ResultForm: TSetupForm;
  Memo: TNewMemo;
  OKButton: TNewButton;
begin
  // Create a modal results dialog
  ResultForm := CreateCustomForm;
  ResultForm.Caption := Title;
  ResultForm.ClientWidth := 500;
  ResultForm.ClientHeight := 400;
  ResultForm.Position := poScreenCenter;

  Memo := TNewMemo.Create(ResultForm);
  Memo.Parent := ResultForm;
  Memo.Left := 10;
  Memo.Top := 10;
  Memo.Width := ResultForm.ClientWidth - 20;
  Memo.Height := ResultForm.ClientHeight - 60;
  Memo.ScrollBars := ssVertical;
  Memo.ReadOnly := True;
  Memo.Text := Content;
  Memo.Font.Name := 'Consolas';
  Memo.Font.Size := 9;

  OKButton := TNewButton.Create(ResultForm);
  OKButton.Parent := ResultForm;
  OKButton.Caption := 'OK';
  OKButton.Width := 80;
  OKButton.Height := 30;
  OKButton.Left := (ResultForm.ClientWidth - OKButton.Width) div 2;
  OKButton.Top := ResultForm.ClientHeight - 45;
  OKButton.ModalResult := mrOK;
  OKButton.Default := True;

  ResultForm.ActiveControl := OKButton;
  ResultForm.ShowModal;
  ResultForm.Free;
end;

// ============================================================================
// MENU OPERATIONS
// ============================================================================

procedure DoViewMappings;
var
  Mappings: String;
begin
  Mappings := GetCurrentDriveMappings(False);
  if Mappings = '' then
    Mappings := 'No output received from PowerShell script.';
  ShowResultsInMemo('Current Network Drive Mappings', Mappings);
end;

procedure DoBackupMappings;
var
  Success: Boolean;
  BackupDate, MappingsList: String;
begin
  // Check for existing backup
  if FileExists(BackupFilePath) then
  begin
    if LoadBackupInfo(BackupDate, MappingsList) then
    begin
      if MsgBox('Existing backup found from: ' + BackupDate + #13#10 +
                'Do you want to overwrite it?', mbConfirmation, MB_YESNO) = IDNO then
      begin
        ShowResultsInMemo('Backup', 'Backup cancelled - keeping existing backup from ' + BackupDate);
        Exit;
      end;
    end;
  end;

  Success := BackupDriveMappings;
  if Success then
    ShowResultsInMemo('Backup Complete', 'Drive mappings saved to:' + #13#10 + BackupFilePath)
  else
    ShowResultsInMemo('Backup Failed', 'Could not save backup to:' + #13#10 + BackupFilePath + #13#10#13#10 +
                      'BackupFilePath: ' + BackupFilePath);
end;

procedure DoTestCredentials;
var
  ErrorCode: Integer;
  ErrorMsg: String;
  Success: Boolean;
  Servers: TArrayOfString;
  I: Integer;
  Results: String;
begin
  if CurrentPassword = '' then
  begin
    MsgBox('Please enter your password first.', mbError, MB_OK);
    Exit;
  end;

  ProgressPage.SetText('Testing credentials...', '');
  ProgressPage.Show;

  try
    Servers := GetLegacyServers;
    Results := 'Testing credentials for: ' + LegacyUsername + NL + NL;

    for I := 0 to GetArrayLength(Servers) - 1 do
    begin
      ProgressPage.SetText('Testing ' + Servers[I] + '...', '');
      ProgressPage.SetProgress((I + 1) * 100 div GetArrayLength(Servers), 100);

      Success := TestCredentials(Servers[I], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg);

      if Success then
        Results := Results + Servers[I] + ': OK' + NL
      else
      begin
        Results := Results + Servers[I] + ': FAILED' + NL;
        Results := Results + '  Error ' + IntToStr(ErrorCode) + ': ' + ErrorMsg + NL;

        // Handle specific errors
        if ErrorCode = 1219 then
        begin
          Results := Results + NL + 'Clearing existing connections...' + NL;
          Results := Results + ClearAllLegacyConnections + NL;
          Results := Results + NL + 'Please try testing again.' + NL;
        end
        else if (ErrorCode = 86) or (ErrorCode = 1326) or (ErrorCode = 1330) then
        begin
          Results := Results + NL + 'Your password may need to be reset.' + NL;
          Results := Results + 'Use option 6 to open the password reset portal.' + NL;
        end
        else if ErrorCode = 53 then
        begin
          Results := Results + NL + 'Please re-authenticate to Zscaler and try again.' + NL;
        end;
      end;
    end;

    ShowResultsInMemo('Credential Test Results', Results);
  finally
    ProgressPage.Hide;
  end;
end;

procedure DoFullReset;
var
  ErrorCode: Integer;
  ErrorMsg: String;
  Success: Boolean;
  Servers: TArrayOfString;
  Results: String;
begin
  if CurrentPassword = '' then
  begin
    MsgBox('Please enter your password first.', mbError, MB_OK);
    Exit;
  end;

  if MsgBox('This will:' + NL +
            '1. Backup your current drive mappings' + NL +
            '2. Test your credentials' + NL +
            '3. Disconnect all legacy drives' + NL +
            '4. Clear cached credentials' + NL +
            '5. Reconnect drives with new credentials' + NL +
            'Continue?', mbConfirmation, MB_YESNO) = IDNO then
    Exit;

  ProgressPage.Show;
  Results := '';

  try
    // Step 1: Backup
    ProgressPage.SetText('Step 1/5: Backing up current mappings...', '');
    ProgressPage.SetProgress(10, 100);

    if BackupDriveMappings then
      Results := Results + '[OK] Backup saved to ' + BackupFilePath + NL
    else
      Results := Results + '[WARN] Backup failed - continuing anyway' + NL;

    // Step 2: Test credentials
    ProgressPage.SetText('Step 2/5: Testing credentials...', '');
    ProgressPage.SetProgress(30, 100);

    Servers := GetLegacyServers;
    Success := TestCredentials(Servers[0], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg);

    if not Success then
    begin
      if ErrorCode = 1219 then
      begin
        Results := Results + '[INFO] Clearing existing connections first...' + NL;
        ClearAllLegacyConnections;
        // Retry
        Success := TestCredentials(Servers[0], LegacyUsername, CurrentPassword, ErrorCode, ErrorMsg);
      end;
    end;

    if Success then
      Results := Results + '[OK] Credentials validated' + NL
    else
    begin
      Results := Results + '[FAILED] Credential test failed: ' + ErrorMsg + NL;
      Results := Results + NL + 'Please fix the credential issue before continuing.' + NL;
      ShowResultsInMemo('Full Reset - Aborted', Results);
      Exit;
    end;

    // Step 3: Clear connections
    ProgressPage.SetText('Step 3/5: Clearing existing connections...', '');
    ProgressPage.SetProgress(50, 100);

    Results := Results + NL + ClearAllLegacyConnections + NL;

    // Step 4: Wait a moment
    ProgressPage.SetText('Step 4/5: Waiting for connections to clear...', '');
    ProgressPage.SetProgress(70, 100);
    Sleep(2000);

    // Step 5: Reconnect
    ProgressPage.SetText('Step 5/5: Reconnecting drives...', '');
    ProgressPage.SetProgress(90, 100);

    Results := Results + NL + 'Reconnecting drives:' + NL;
    Results := Results + ReconnectDrivesFromBackup(LegacyUsername, CurrentPassword);

    ProgressPage.SetProgress(100, 100);

    Results := Results + NL + '=== Full Reset Complete ===' + NL;
    ShowResultsInMemo('Full Reset Results', Results);

  finally
    ProgressPage.Hide;
  end;
end;

procedure DoRestoreFromBackup;
var
  BackupDate, MappingsList: String;
  Results: String;
begin
  if CurrentPassword = '' then
  begin
    MsgBox('Please enter your password first.', mbError, MB_OK);
    Exit;
  end;

  if not FileExists(BackupFilePath) then
  begin
    MsgBox('No backup file found at:' + NL + BackupFilePath, mbError, MB_OK);
    Exit;
  end;

  if not LoadBackupInfo(BackupDate, MappingsList) then
  begin
    MsgBox('Could not read backup file.', mbError, MB_OK);
    Exit;
  end;

  if MsgBox('Restore drives from backup dated: ' + BackupDate + '?',
            mbConfirmation, MB_YESNO) = IDNO then
    Exit;

  ProgressPage.SetText('Restoring drives from backup...', '');
  ProgressPage.SetProgress(50, 100);
  ProgressPage.Show;

  try
    Results := 'Restoring from backup dated: ' + BackupDate + NL + NL;
    Results := Results + ReconnectDrivesFromBackup(LegacyUsername, CurrentPassword);
    ShowResultsInMemo('Restore Results', Results);
  finally
    ProgressPage.Hide;
  end;
end;

procedure DoAddDrives;
var
  DriveForm: TSetupForm;
  InfoLabel: TLabel;
  ChkDriveS, ChkDriveV, ChkDriveY: TNewCheckBox;
  OKButton, CancelButton: TNewButton;
  Results: String;
  Script: String;
  Output: String;
  AddS, AddV, AddY: Boolean;
begin
  if CurrentPassword = '' then
  begin
    MsgBox('Please enter your password first.', mbError, MB_OK);
    Exit;
  end;

  // Create drive selection dialog
  DriveForm := CreateCustomForm;
  DriveForm.Caption := 'Add Network Drives';
  DriveForm.ClientWidth := 450;
  DriveForm.ClientHeight := 280;
  DriveForm.Position := poScreenCenter;

  InfoLabel := TLabel.Create(DriveForm);
  InfoLabel.Parent := DriveForm;
  InfoLabel.Left := 20;
  InfoLabel.Top := 20;
  InfoLabel.Width := 410;
  InfoLabel.Height := 40;
  InfoLabel.AutoSize := False;
  InfoLabel.WordWrap := True;
  InfoLabel.Caption := 'Select the network drives you want to add. Existing drives with the same letter will be replaced.';

  ChkDriveS := TNewCheckBox.Create(DriveForm);
  ChkDriveS.Parent := DriveForm;
  ChkDriveS.Left := 20;
  ChkDriveS.Top := 70;
  ChkDriveS.Width := 410;
  ChkDriveS.Height := 20;
  ChkDriveS.Caption := DRIVE_S_DESC;
  ChkDriveS.Checked := True;

  ChkDriveV := TNewCheckBox.Create(DriveForm);
  ChkDriveV.Parent := DriveForm;
  ChkDriveV.Left := 20;
  ChkDriveV.Top := 100;
  ChkDriveV.Width := 410;
  ChkDriveV.Height := 20;
  ChkDriveV.Caption := DRIVE_V_DESC;
  ChkDriveV.Checked := False;

  ChkDriveY := TNewCheckBox.Create(DriveForm);
  ChkDriveY.Parent := DriveForm;
  ChkDriveY.Left := 20;
  ChkDriveY.Top := 130;
  ChkDriveY.Width := 410;
  ChkDriveY.Height := 20;
  ChkDriveY.Caption := DRIVE_Y_DESC;
  ChkDriveY.Checked := False;

  OKButton := TNewButton.Create(DriveForm);
  OKButton.Parent := DriveForm;
  OKButton.Caption := 'Add Selected Drives';
  OKButton.Width := 130;
  OKButton.Height := 30;
  OKButton.Left := 100;
  OKButton.Top := DriveForm.ClientHeight - 50;
  OKButton.ModalResult := mrOK;
  OKButton.Default := True;

  CancelButton := TNewButton.Create(DriveForm);
  CancelButton.Parent := DriveForm;
  CancelButton.Caption := 'Cancel';
  CancelButton.Width := 80;
  CancelButton.Height := 30;
  CancelButton.Left := 250;
  CancelButton.Top := DriveForm.ClientHeight - 50;
  CancelButton.ModalResult := mrCancel;

  if DriveForm.ShowModal = mrOK then
  begin
    AddS := ChkDriveS.Checked;
    AddV := ChkDriveV.Checked;
    AddY := ChkDriveY.Checked;
    DriveForm.Free;

    if not (AddS or AddV or AddY) then
    begin
      ShowResultsInMemo('Add Drives', 'No drives selected.');
      Exit;
    end;

    Results := 'Adding network drives...' + #13#10#13#10;

    // Build PowerShell script using three-phase approach to avoid error 1219
    // when multiple drives point to the same server (e.g., S: and V: both on tsgwp00525)
    Script :=
      '$Username = "' + LegacyUsername + '"' + NL +
      '$Password = "' + CurrentPassword + '"' + NL +
      '$results = @()' + NL +
      '$ManagedServers = @("' + NEW_SERVER_1 + '", "' + DRIVE_Y_SERVER + '")' + NL + NL +
      '# Collect drives to add' + NL +
      '$drivesToAdd = @()' + NL;

    if AddS then
      Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "S:"; Path = "' + DRIVE_S_PATH + '" }' + NL;

    if AddV then
      Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "V:"; Path = "' + DRIVE_V_PATH + '" }' + NL;

    if AddY then
      Script := Script + '$drivesToAdd += [PSCustomObject]@{ Letter = "Y:"; Path = "' + DRIVE_Y_PATH + '" }' + NL;

    Script := Script + NL +
      '# PHASE 1: Disconnect ALL selected drives first' + NL +
      '$results += "Phase 1: Disconnecting existing drives..."' + NL +
      'foreach ($drive in $drivesToAdd) {' + NL +
      '    $null = net use $drive.Letter /delete /y 2>&1' + NL +
      '}' + NL + NL +
      '# PHASE 2: Clear IPC$ connections to prevent error 1219' + NL +
      '$results += "Phase 2: Clearing server connections..."' + NL +
      'foreach ($server in $ManagedServers) {' + NL +
      '    $null = net use "\\$server\IPC$" /delete /y 2>&1' + NL +
      '    $null = net use "\\$server" /delete /y 2>&1' + NL +
      '    $null = cmdkey /delete:$server 2>&1' + NL +
      '}' + NL + NL +
      '# Small delay to let Windows release connections' + NL +
      'Start-Sleep -Seconds 2' + NL + NL +
      '# PHASE 3: Reconnect ALL drives' + NL +
      '$results += "Phase 3: Connecting drives..."' + NL +
      '$results += ""' + NL +
      'foreach ($drive in $drivesToAdd) {' + NL +
      '    $output = net use $drive.Letter $drive.Path /user:$Username $Password /persistent:yes 2>&1' + NL +
      '    if ($LASTEXITCODE -eq 0) {' + NL +
      '        $results += "OK: $($drive.Letter) -> $($drive.Path)"' + NL +
      '    } else {' + NL +
      '        $errCode = $LASTEXITCODE' + NL +
      '        $outStr = $output | Out-String' + NL +
      '        if ($outStr -match "System error (\d+)") { $errCode = [int]$Matches[1] }' + NL +
      '        $results += "FAILED: $($drive.Letter) -> $($drive.Path) (Error $errCode)"' + NL +
      '    }' + NL +
      '}' + NL + NL +
      '$results | ForEach-Object { Write-Output $_ }';

    ExecPowerShell(Script, Output);
    Results := Results + Output;
    ShowResultsInMemo('Add Drives Results', Results);
  end
  else
    DriveForm.Free;
end;

procedure DoOpenPasswordPortal;
var
  ErrorCode: Integer;
begin
  ShellExec('open', PASSWORD_RESET_URL, '', '', SW_SHOW, ewNoWait, ErrorCode);
  ShowResultsInMemo('Password Portal',
    'Browser opened to: ' + PASSWORD_RESET_URL + NL +
    'After resetting your password, wait 10 minutes for' + NL +
    'the change to sync to the legacy domain, then return' + NL +
    'here and enter your new password.');
end;

// ============================================================================
// MENU CLICK HANDLER
// ============================================================================

procedure MenuListBoxClick(Sender: TObject);
begin
  SelectedMenuOption := MenuListBox.ItemIndex + 1;
end;

procedure MenuListBoxDblClick(Sender: TObject);
begin
  SelectedMenuOption := MenuListBox.ItemIndex + 1;
  WizardForm.NextButton.OnClick(WizardForm.NextButton);
end;

// ============================================================================
// PASSWORD PAGE HANDLING
// ============================================================================

procedure PasswordPageOnChange(Sender: TObject);
begin
  CurrentPassword := PasswordPage.Values[0];
end;

// ============================================================================
// WIZARD PAGE CREATION
// ============================================================================

procedure InitializeWizard;
var
  MenuLabel: TLabel;
  PasswordLabel: TLabel;
  UsernameLabel: TLabel;
begin
  // Initialize globals
  LegacyUsername := GetLegacyUsername;
  BackupFilePath := GetBackupFilePath;
  CurrentPassword := '';
  SelectedMenuOption := 0;

  // =========================================
  // Main Menu Page
  // =========================================
  MainMenuPage := CreateCustomPage(wpWelcome,
    'Network Drive Manager',
    'Select an option from the menu below');

  // Status/info label at top
  StatusLabel := TLabel.Create(MainMenuPage);
  StatusLabel.Parent := MainMenuPage.Surface;
  StatusLabel.Left := 0;
  StatusLabel.Top := 0;
  StatusLabel.Width := MainMenuPage.SurfaceWidth;
  StatusLabel.Height := 50;
  StatusLabel.AutoSize := False;
  StatusLabel.WordWrap := True;
  StatusLabel.Caption := 'User: ' + GetUserNameString + #13#10 +
                         'Legacy Account: ' + LegacyUsername + #13#10 +
                         'Backup: ' + BackupFilePath;

  // Menu label
  MenuLabel := TLabel.Create(MainMenuPage);
  MenuLabel.Parent := MainMenuPage.Surface;
  MenuLabel.Left := 0;
  MenuLabel.Top := 60;
  MenuLabel.Caption := 'Select an option:';
  MenuLabel.Font.Style := [fsBold];

  // Menu list box
  MenuListBox := TNewListBox.Create(MainMenuPage);
  MenuListBox.Parent := MainMenuPage.Surface;
  MenuListBox.Left := 0;
  MenuListBox.Top := 80;
  MenuListBox.Width := MainMenuPage.SurfaceWidth;
  MenuListBox.Height := 180;
  MenuListBox.Items.Add('1. View current network drive mappings');
  MenuListBox.Items.Add('2. Backup current mappings');
  MenuListBox.Items.Add('3. Test legacy credentials');
  MenuListBox.Items.Add('4. Full reset - Disconnect, clear creds, and reconnect');
  MenuListBox.Items.Add('5. Restore drives from backup');
  MenuListBox.Items.Add('6. Add new network drives (S:, V:, Y:)');
  MenuListBox.Items.Add('7. Open password reset portal');
  MenuListBox.Items.Add('8. Quit');
  MenuListBox.ItemIndex := 0;
  MenuListBox.OnClick := @MenuListBoxClick;
  MenuListBox.OnDblClick := @MenuListBoxDblClick;

  // =========================================
  // Password Page
  // =========================================
  PasswordPage := CreateInputQueryPage(MainMenuPage.ID,
    'Enter Legacy Password',
    'Enter your legacy domain password to continue',
    'This password will be used to test credentials and reconnect drives.' + #13#10 +
    'Your password is not stored permanently.');

  PasswordPage.Add('Legacy Password:', True);
  PasswordPage.Edits[0].OnChange := @PasswordPageOnChange;

  // Add username display
  UsernameLabel := TLabel.Create(PasswordPage);
  UsernameLabel.Parent := PasswordPage.Surface;
  UsernameLabel.Left := 0;
  UsernameLabel.Top := 0;
  UsernameLabel.Caption := 'Username: ' + LegacyUsername;
  UsernameLabel.Font.Style := [fsBold];

  // =========================================
  // Progress Page
  // =========================================
  ProgressPage := CreateOutputProgressPage('Working...', 'Please wait while the operation completes.');

  // =========================================
  // Results Page (comes after password page)
  // =========================================
  ResultsPage := CreateOutputMsgMemoPage(PasswordPage.ID,
    'Results',
    'Operation complete',
    'Results:',
    '');
  ResultsPage.RichEditViewer.ScrollBars := ssVertical;

  // =========================================
  // Auto-backup on startup
  // =========================================
  if FileExists(BackupFilePath) then
  begin
    // Backup exists - will ask on first backup operation
  end
  else
  begin
    // No backup - create one automatically
    if BackupDriveMappings then
      StatusLabel.Caption := StatusLabel.Caption + NL + '[Auto-backup created]';
  end;
end;

// ============================================================================
// PAGE NAVIGATION
// ============================================================================

function ShouldSkipPage(PageID: Integer): Boolean;
begin
  Result := False;

  // Skip password page for operations that don't need it
  if PageID = PasswordPage.ID then
  begin
    case SelectedMenuOption of
      MENU_VIEW_MAPPINGS, MENU_BACKUP, MENU_PASSWORD_PORTAL, MENU_QUIT:
        Result := True;
      // These require password: MENU_TEST_CREDS, MENU_FULL_RESET, MENU_RESTORE, MENU_ADD_DRIVES
    end;
  end;

  // Always skip results page - we use modal dialogs now
  if PageID = ResultsPage.ID then
    Result := True;
end;

function NextButtonClick(CurPageID: Integer): Boolean;
begin
  Result := True;

  if CurPageID = MainMenuPage.ID then
  begin
    SelectedMenuOption := MenuListBox.ItemIndex + 1;

    // Handle quit immediately
    if SelectedMenuOption = MENU_QUIT then
    begin
      WizardForm.Close;
      Result := False;
      Exit;
    end;

    // Handle operations that don't need password - stay on main menu
    case SelectedMenuOption of
      MENU_VIEW_MAPPINGS:
        begin
          DoViewMappings;
          Result := False; // Stay on main menu
        end;
      MENU_BACKUP:
        begin
          DoBackupMappings;
          Result := False; // Stay on main menu
        end;
      MENU_PASSWORD_PORTAL:
        begin
          DoOpenPasswordPortal;
          Result := False; // Stay on main menu
        end;
    end;
    // For other options (3,4,5), proceed to password page
  end
  else if CurPageID = PasswordPage.ID then
  begin
    // Validate password entered
    if CurrentPassword = '' then
    begin
      MsgBox('Please enter your password.', mbError, MB_OK);
      Result := False;
      Exit;
    end;

    // Execute the selected operation
    case SelectedMenuOption of
      MENU_TEST_CREDS:
        DoTestCredentials;
      MENU_FULL_RESET:
        DoFullReset;
      MENU_RESTORE:
        DoRestoreFromBackup;
      MENU_ADD_DRIVES:
        DoAddDrives;
    end;

    // Clear password and go back to main menu
    CurrentPassword := '';
    PasswordPage.Values[0] := '';
    Result := False; // Don't proceed forward

    // Navigate back to main menu
    WizardForm.BackButton.OnClick(WizardForm.BackButton);
  end;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
  // Customize button text based on page
  if CurPageID = MainMenuPage.ID then
  begin
    WizardForm.NextButton.Caption := 'Select';
    WizardForm.BackButton.Visible := False;
    WizardForm.CancelButton.Caption := 'Quit';
  end
  else if CurPageID = PasswordPage.ID then
  begin
    WizardForm.NextButton.Caption := 'Continue';
    WizardForm.BackButton.Caption := 'Back';
    WizardForm.BackButton.Visible := True;
  end;
end;
