Functions/GenXdev.Coding.PowerShell.Modules/Assert-RefactorFile.ps1

################################################################################
<#
.SYNOPSIS
Executes a refactoring operation on a source file using the specified IDE and
AI prompt template.
 
.DESCRIPTION
This function automates the refactoring process by preparing an AI prompt based
on the refactor definition, detecting or selecting the appropriate IDE (VS Code
or Visual Studio), and opening the target file with the prepared prompt. The
function handles prompt template processing, IDE detection, and automation of
the refactoring workflow.
 
.PARAMETER RefactorDefinition
The refactor definition object containing settings, prompt templates, and IDE
preferences for the refactoring operation.
 
.PARAMETER Path
The absolute or relative path to the source file that needs to be refactored.
The path will be expanded to an absolute path during processing.
 
.EXAMPLE
$refactorDef = Get-RefactorDefinition -Type "Documentation"
Assert-RefactorFile -RefactorDefinition $refactorDef -Path "MyScript.ps1"
 
.EXAMPLE
Assert-RefactorFile -RefactorDefinition $refactorDef -Path ".\Functions\Test.ps1"
#>

function Assert-RefactorFile {

    [CmdletBinding()]
    param(
        ###############################################################################
        [Parameter(
            Mandatory = $true,
            Position = 0,
            HelpMessage = 'The path to the source file to improve'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('FullName')]
        [string] $Path,
        ###############################################################################
        [Parameter(
            Mandatory = $false,
            Position = 1,
            HelpMessage = 'The refactor definition containing settings and prompt template'
        )]
        [ValidateNotNull()]
        [GenXdev.Helpers.RefactorSettings] $RefactorSettings = [GenXdev.Helpers.RefactorSettings]::new(),
        ###############################################################################
        [switch] $AllowLongRunningTests
    )

    begin {

        # prepare paths for scripts and modules directories
        $scriptsPath = GenXdev.FileSystem\Expand-Path `
            "$PSScriptRoot\..\..\..\..\..\Scripts\" `
            -CreateDirectory

        $modulesPath = GenXdev.FileSystem\Expand-Path `
            "$PSScriptRoot\..\..\..\..\" `
            -CreateDirectory

        # expand the target file path to absolute path
        $Path = GenXdev.FileSystem\Expand-Path $Path

        # extract prompt settings from the refactor definition
        $prompt = $RefactorSettings.Prompt

        $promptKey = $RefactorSettings.PromptKey

        # detect which IDE is currently active by examining running processes
        [System.Diagnostics.Process] $hostProcess = `
            GenXdev.Windows\Get-PowershellMainWindowProcess

        $isCode = $hostProcess.Name -eq 'Code'

        $isVisualStudio = $hostProcess.Name -eq 'devenv'

        # fallback to refactor definition preferences if no active IDE found
        if (-not ($isCode -or $isVisualStudio)) {

            # check if VS Code is preferred in settings
            if ($RefactorSettings.Code -ge 0) {

                $isCode = $RefactorSettings.Code -eq 1
            }

            # check if Visual Studio is preferred in settings
            if ($RefactorSettings.VisualStudio -ge 0) {

                $isVisualStudio = $RefactorSettings.VisualStudio -eq 1
            }

            # attempt to find any running IDE process as final fallback
            if (-not ($isCode -or $isVisualStudio)) {

                Microsoft.PowerShell.Utility\Write-Verbose (
                    'Attempting to detect running IDE instances')

                # search for running VS Code processes
                [System.Diagnostics.Process] $hostProcess = `
                    Microsoft.PowerShell.Management\Get-Process 'Code' `
                        -ErrorAction SilentlyContinue |
                        Microsoft.PowerShell.Utility\Sort-Object |
                        Microsoft.PowerShell.Utility\Select-Object -First 1

                $isCode = $null -ne $hostProcess

                # search for running Visual Studio processes
                $hostProcess = Microsoft.PowerShell.Management\Get-Process 'devenv' `
                    -ErrorAction SilentlyContinue |
                    Microsoft.PowerShell.Utility\Sort-Object |
                    Microsoft.PowerShell.Utility\Select-Object -First 1

                $isVisualStudio = $null -ne $hostProcess
            }
        }

        # prompt user to select IDE if no clear choice can be determined
        if (-not ($isCode -bxor $isVisualStudio)) {

            Microsoft.PowerShell.Utility\Write-Verbose 'Prompting user to select IDE'

            # use cached selection or prompt for new choice
            $userAnswer = $null -ne $Script:_CodeOrVisualStudioRefactor ?
                $Script:_CodeOrVisualStudioRefactor :
                ($host.ui.PromptForChoice(
                    'Make a choice',
                    'What IDE to use for refactoring?',
                    @('Visual Studio &Code', '&Visual Studio'),
                    0))

            # cache the user's selection for future operations
            $Script:_CodeOrVisualStudioRefactor = $userAnswer

            # apply the user's IDE selection
            switch ($userAnswer) {

                0 {
                    $isCode = $true

                    $isVisualStudio = $false

                    break;
                }
                1 {
                    $isCode = $false

                    $isVisualStudio = $true

                    break;
                }
            }
        }

        # process and load prompt template if a template key is specified
        if (-not [string]::IsNullOrWhiteSpace($PromptKey)) {

            # construct the base path for prompt templates
            $promptFilePath = GenXdev.FileSystem\Expand-Path `
                "$PSScriptRoot\..\..\Prompts\GenXdev.Coding.PowerShell.Modules\" `
                -CreateDirectory

            # build the template file path using the prompt key
            $promptFilePath = Microsoft.PowerShell.Management\Join-Path `
                $promptFilePath "Assert-$PromptKey.txt"

            # use script-specific template if target file is in scripts folder
            if ($Path -like "$scriptsPath\*.ps1") {

                $promptFilePath = GenXdev.FileSystem\Expand-Path (
                    "$PSScriptRoot\..\..\Prompts\GenXdev.Coding.PowerShell.Modules\" +
                    "Assert-$PromptKey-script.txt") -CreateFile
            }

            # load the template file and replace the prompt placeholder
            $Prompt = [System.IO.File]::ReadAllText($promptFilePath).Replace(
                "`$Prompt",
                $Prompt
            )
        }

        # replace template variables with actual values from the target file
        $Prompt = $Prompt.Replace(
            "`$CmdletName",
            [System.IO.Path]::GetFileNameWithoutExtension($Path)
        )

        # remove test suffix from cmdlet name for cleaner prompts
        $Prompt = $Prompt.Replace(
            "`$CmdLetNoTestName",
            [System.IO.Path]::GetFileNameWithoutExtension($Path).Replace(
                '.Tests', ''
            )
        )

        # insert the script filename into the prompt
        $Prompt = $Prompt.Replace(
            "`$ScriptFileName",
            [System.IO.Path]::GetFileName($Path)
        )

        # normalize tab characters to spaces for consistent formatting
        $Prompt = $Prompt.Replace("`t", ' ')

        # determine module context based on file location
        if ($Path.ToLowerInvariant().StartsWith($scriptsPath.ToLowerInvariant())) {

            # handle files in the scripts directory
            $Prompt = $Prompt.Replace(
                "`$FullModuleName",
                'GenXdev.Scripts'
            )

            $Prompt = $Prompt.Replace(
                "`$BaseModuleName",
                ''
            )
        }
        else {

            # extract base module name from the file path
            $baseModuleName = `
                "$($Path.Substring($modulesPath.Length + 1).Split('\')[0])"

            # construct paths for functions and tests directories
            $functionsPath = GenXdev.FileSystem\Expand-Path `
                "$modulesPath\$baseModuleName\1.238.2025\Functions\" `
                -CreateDirectory

            $testsPath = GenXdev.FileSystem\Expand-Path `
                "$modulesPath\$baseModuleName\1.238.2025\Tests\" `
                -CreateDirectory

            # determine the specific module context based on file location
            if ($Path.ToLowerInvariant().StartsWith($functionsPath.ToLowerInvariant())) {

                # extract sub-module name from functions path
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$($Path.Substring($functionsPath.Length + 1).Split('\')[0])"
                )
            }
            elseif ($Path.ToLowerInvariant().StartsWith($testsPath.ToLowerInvariant())) {

                # extract sub-module name from tests path
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$($Path.Substring($testsPath.Length + 1).Split('\')[0])"
                )
            }
            else {

                # use base module name for other locations
                $Prompt = $Prompt.Replace(
                    "`$FullModuleName",
                    "$baseModuleName"
                )
            }

            # set the base module name in the prompt
            $Prompt = $Prompt.Replace(
                "`$BaseModuleName",
                "$baseModuleName"
            )
        }

        # final normalization of tab characters
        $Prompt = $Prompt.Replace("`t", ' ')

        # preserve current clipboard content for later restoration
        $previousClipboard = Microsoft.PowerShell.Management\Get-Clipboard

        # copy the prepared prompt to clipboard for use in IDE
        $null = Microsoft.PowerShell.Management\Set-Clipboard -Value $prompt

        Microsoft.PowerShell.Utility\Write-Verbose (
            'Prepared prompt and copied to clipboard:')

        Microsoft.PowerShell.Utility\Write-Verbose $prompt

        # determine keyboard automation sequence based on selected IDE
        $keysToSend = $RefactorSettings.KeysToSend

        # use default key sequences if none specified in settings
        if (($null -eq $keysToSend) -or ($keysToSend.Count -eq 0)) {

            # configure VS Code key sequence for AI assistant integration
            if ($isCode) {

                $keysToSend = @("^``", "^``", '^+i', '^l', '^a', '{DELETE}',
                    '^+i', '{ESCAPE}', '^+%{F12}', '{ENTER}', '^v',
                    '{ENTER}', '^{ENTER}',"^``")
            }
            elseif ($isVisualStudio) {

                # configure Visual Studio key sequence for copilot integration
                $keysToSend = @('^\', '^c', '^a', ' {DELETE}', '^v',
                    '{ENTER}', '^{ENTER}')
            }
        }
    }


    process {

        Microsoft.PowerShell.Utility\Write-Verbose (
            'Opening file in IDE for refactoring')

        # prepare parameter hashtable for IDE invocation
        $invocationParams = @{
            Path       = $Path
            KeysToSend = $keysToSend
        }

        # add IDE-specific parameters if they exist in current scope
        $invocationParams.Code = $Code

        $invocationParams.VisualStudio = $VisualStudio

        # launch the selected IDE with automation parameters
        GenXdev.Coding\Open-SourceFileInIde @invocationParams
    }

    end {

        # restore the original clipboard content to maintain user workflow
        $null = Microsoft.PowerShell.Management\Set-Clipboard -Value $previousClipboard
    }
}
################################################################################