Public/Core/ConvertFrom-AMJson.ps1

function ConvertFrom-AMJson {
    <#
    .SYNOPSIS
        Converts an Adaptive Card JSON to PowerShell commands using the ActionableMessages module.
 
    .DESCRIPTION
        Takes an Adaptive Card JSON string and generates the equivalent PowerShell commands
        that would create the same card using the ActionableMessages module functions.
 
        This function is useful for:
        - Converting existing Adaptive Cards to PowerShell code
        - Learning by example how to create complex cards
        - Migrating from other platforms that export Adaptive Cards as JSON
        - Generating scripts from designer-created cards
 
        The generated code follows best practices for the ActionableMessages module
        and maintains proper nesting of elements within containers and column sets.
 
    .PARAMETER Json
        The Adaptive Card JSON string to convert to PowerShell commands.
        This can be a complete Adaptive Card JSON object with schema, type, version, etc.
 
    .PARAMETER OutputPath
        Optional. If specified, writes the generated PowerShell script to this file path
        instead of returning it as a string.
 
    .PARAMETER GenerateId
        Optional switch. When specified, generates new IDs for elements that don't have them,
        which can be useful when you need to reference elements later in your code.
 
    .EXAMPLE
        # Convert JSON from a file and display the PowerShell commands
        $jsonContent = Get-Content -Path ".\myAdaptiveCard.json" -Raw
        ConvertFrom-AMJson -Json $jsonContent
 
    .EXAMPLE
        # Convert JSON and save the PowerShell commands to a file
        $jsonContent = Get-Content -Path ".\designerCard.json" -Raw
        ConvertFrom-AMJson -Json $jsonContent -OutputPath ".\createCard.ps1"
 
    .EXAMPLE
        # Convert JSON from a web response
        $response = Invoke-RestMethod -Uri "https://myapi.example.com/cards/template"
        $response.cardJson | ConvertFrom-AMJson
 
    .EXAMPLE
        # Convert and immediately execute the generated script
        $json = '{"type":"AdaptiveCard","version":"1.2","body":[{"type":"TextBlock","text":"Hello World"}]}'
        $script = ConvertFrom-AMJson -Json $json
        Invoke-Expression $script
        $cardJson # Access the card created by the script
 
    .INPUTS
        System.String
 
    .OUTPUTS
        System.String or None
        Returns the generated PowerShell script as a string if no OutputPath is specified.
        If OutputPath is specified, writes to the file and returns a confirmation message.
 
    .NOTES
        This function will attempt to handle all standard Adaptive Card elements and actions,
        including TextBlocks, Images, ImageSets, Containers, ColumnSets, FactSets, Input elements,
        and various action types.
 
        If the JSON contains unsupported element types, they will be commented in the output script.
 
        Variable names in the generated script are based on element types and IDs when available.
 
    .LINK
        https://adaptivecards.io/explorer/
 
    .LINK
        Export-AMCard
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$Json,

        [Parameter(Mandatory = $false)]
        [string]$OutputPath,

        [Parameter(Mandatory = $false)]
        [switch]$GenerateId
    )

    begin {
        # Track processed elements to avoid duplicates
        $script:processedElements = @{}
        $script:variableCounter = 0

        # Function to generate a unique variable name
        function Get-UniqueVarName {
            param(
                [string]$Type,
                [string]$Id = "",
                [int]$Index = -1
            )

            # Use the element ID if provided, otherwise generate a sequential name
            if ($Id -and -not [string]::IsNullOrWhiteSpace($Id)) {
                $baseName = "$($Type.ToLower())_$($Id -replace '[^a-zA-Z0-9]', '_')"
            }
            elseif ($Index -ge 0) {
                $baseName = "$($Type.ToLower())_$Index"
            }
            else {
                $script:variableCounter++
                $baseName = "$($Type.ToLower())_$($script:variableCounter)"
            }

            # Make sure the name is unique
            if ($script:processedElements.ContainsKey($baseName)) {
                $script:variableCounter++
                $baseName = "${baseName}_$($script:variableCounter)"
            }

            # Register this name as used
            $script:processedElements[$baseName] = $true

            return $baseName
        }

        # Add a line to the output script
        function Add-ScriptLine {
            param(
                [string]$Line,
                [int]$IndentLevel = 0
            )

            $indent = " " * $IndentLevel
            $script:output += "$indent$Line`r`n"
        }

        $script:output = ""

        # Process elements in order - to ensure dependencies are created first
        function Process-Card {
            param($CardObject)

            # Start with the card
            Add-ScriptLine "# Create a new card"
            $cardParams = @()
            if ($CardObject.originator) {
                $cardParams += "-OriginatorId `"$($CardObject.originator)`""
            }
            if ($CardObject.version) {
                $cardParams += "-Version `"$($CardObject.version)`""
            }
            Add-ScriptLine "`$card = New-AMCard $($cardParams -join ' ')"
            Add-ScriptLine ""

            # Process all elements
            Add-ScriptLine "# Add elements to the card"

            # Main body elements
            if ($CardObject.body -and $CardObject.body.Count -gt 0) {
                foreach ($element in $CardObject.body) {
                    Process-Element -Element $element
                }
            }
        }

        # Process any element by type
        function Process-Element {
            param(
                $Element,
                [string]$ContainerId = $null
            )

            switch ($Element.type) {
                "TextBlock" { Process-TextBlock -Element $Element -ContainerId $ContainerId }
                "Image" { Process-Image -Element $Element -ContainerId $ContainerId }
                "ImageSet" { Process-ImageSet -Element $Element -ContainerId $ContainerId }
                "Container" { Process-Container -Element $Element -ContainerId $ContainerId }
                "ColumnSet" { Process-ColumnSet -Element $Element -ContainerId $ContainerId }
                "FactSet" { Process-FactSet -Element $Element -ContainerId $ContainerId }
                "ActionSet" { Process-ActionSet -Element $Element -ContainerId $ContainerId }
                default {
                    if ($Element.type -match "Input\.") {
                        Process-Input -Element $Element -ContainerId $ContainerId
                    }
                    else {
                        Add-ScriptLine "# Unsupported element type: $($Element.type)"
                    }
                }
            }
        }

        # Process TextBlock element
        function Process-TextBlock {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "textBlock" -Id $Element.id
            $params = @("-Text `"$($Element.text)`"")

            if ($Element.size -and $Element.size -ne "Medium") {
                $params += "-Size `"$($Element.size)`""
            }
            if ($Element.weight -and $Element.weight -ne "Default") {
                $params += "-Weight `"$($Element.weight)`""
            }
            if ($Element.color -and $Element.color -ne "Default") {
                $params += "-Color `"$($Element.color)`""
            }
            if ($Element.wrap -and $Element.wrap -eq "True") {
                $params += "-Wrap `$true"
            }

            Add-ScriptLine "`$$varName = New-AMTextBlock $($params -join ' ')"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process Image element
        function Process-Image {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "image" -Id $Element.id
            $params = @()

            if ($Element.url) {
                $params += "-Url `"$($Element.url)`""
            }
            if ($Element.altText) {
                $params += "-AltText `"$($Element.altText)`""
            }
            if ($Element.size) {
                $params += "-Size `"$($Element.size)`""
            }

            Add-ScriptLine "`$$varName = New-AMImage $($params -join ' ')"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process ImageSet element
        function Process-ImageSet {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "imageSet" -Id $Element.id

            Add-ScriptLine "`$images = @("
            foreach ($image in $Element.images) {
                # Add each image URL to the array with a trailing comma except for the last one
                if ($image -eq $Element.images[-1]) {
                    Add-ScriptLine " `"$($image.url)`""
                }
                else {
                    Add-ScriptLine " `"$($image.url)`","
                }
            }
            Add-ScriptLine ")"

            Add-ScriptLine "`$$varName = New-AMImageSet -Images `$images"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process Container element
        function Process-Container {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "container" -Id $Element.id
            $params = @()

            if ($Element.id) {
                $params += "-Id `"$($Element.id)`""
            }
            if ($Element.style) {
                $params += "-Style `"$($Element.style)`""
            }
            if ($Element.padding) {
                $params += "-Padding `"$($Element.padding)`""
            }

            Add-ScriptLine "`$$varName = New-AMContainer $($params -join ' ')"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId

            # Process container items
            if ($Element.items -and $Element.items.Count -gt 0) {
                foreach ($item in $Element.items) {
                    Process-Element -Element $item -ContainerId $Element.id
                }
            }
        }

        # Process ColumnSet element
        function Process-ColumnSet {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "columnSet" -Id $Element.id

            # First process all columns
            $columnVars = @()

            for ($i = 0; $i -lt $Element.columns.Count; $i++) {
                $column = $Element.columns[$i]
                $colVarName = Get-UniqueVarName -Type "column" -Index $i

                # Create column items
                if ($column.items -and $column.items.Count -gt 0) {
                    Add-ScriptLine "`$$colVarName = New-AMColumn -Width `"$($column.width)`" -Items @("

                    foreach ($item in $column.items) {
                        if ($item.type -eq "TextBlock") {
                            $textParams = @("`"$($item.text)`"")

                            if ($item.size -and $item.size -ne "Medium") {
                                $textParams += "-Size `"$($item.size)`""
                            }
                            if ($item.weight -and $item.weight -ne "Default") {
                                $textParams += "-Weight `"$($item.weight)`""
                            }
                            if ($item.color -and $item.color -ne "Default") {
                                $textParams += "-Color `"$($item.color)`""
                            }

                            Add-ScriptLine " (New-AMTextBlock -Text $($textParams -join ' ')),"
                        }
                        else {
                            Add-ScriptLine " # Unsupported column item: $($item.type)"
                        }
                    }

                    # Remove the trailing comma
                    $script:output = $script:output -replace ",\r\n$", "`r`n"
                    Add-ScriptLine ")"
                }
                else {
                    Add-ScriptLine "`$$colVarName = New-AMColumn -Width `"$($column.width)`""
                }

                $columnVars += "`$$colVarName"
            }

            # Now create the ColumnSet
            $csParams = @()
            if ($Element.id) {
                $csParams += "-Id `"$($Element.id)`""
            }
            $csParams += "-Columns @($($columnVars -join ', '))"

            Add-ScriptLine "`$$varName = New-AMColumnSet $($csParams -join ' ')"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process FactSet element
        function Process-FactSet {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "factSet" -Id $Element.id

            Add-ScriptLine "`$facts = @("
            foreach ($fact in $Element.facts) {
                Add-ScriptLine " (New-AMFact -Title `"$($fact.title)`" -Value `"$($fact.value)`"),"
            }
            # Remove the trailing comma
            $script:output = $script:output -replace ",\r\n$", "`r`n"
            Add-ScriptLine ")"

            Add-ScriptLine "`$$varName = New-AMFactSet -Facts `$facts"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process Input elements
        function Process-Input {
            param($Element, [string]$ContainerId = $null)

            $inputType = $Element.type.Replace("Input.", "")
            $varName = Get-UniqueVarName -Type $inputType -Id $Element.id

            switch ($inputType) {
                "Text" {
                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.label) { $params += "-Label `"$($Element.label)`"" }
                    if ($Element.placeholder) { $params += "-Placeholder `"$($Element.placeholder)`"" }
                    if ($Element.maxLength) { $params += "-MaxLength $($Element.maxLength)" }

                    Add-ScriptLine "`$$varName = New-AMTextInput $($params -join ' ')"
                }
                "Number" {
                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.placeholder) { $params += "-Placeholder `"$($Element.placeholder)`"" }
                    if ($Element.min) { $params += "-Min $($Element.min)" }
                    if ($Element.max) { $params += "-Max $($Element.max)" }

                    Add-ScriptLine "`$$varName = New-AMNumberInput $($params -join ' ')"
                }
                "Date" {
                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.label) { $params += "-Label `"$($Element.label)`"" }
                    if ($Element.value) { $params += "-Value `"$($Element.value)`"" }

                    Add-ScriptLine "`$$varName = New-AMDateInput $($params -join ' ')"
                }
                "Time" {
                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.label) { $params += "-Label `"$($Element.label)`"" }
                    if ($Element.value) { $params += "-Value `"$($Element.value)`"" }

                    Add-ScriptLine "`$$varName = New-AMTimeInput $($params -join ' ')"
                }
                "ChoiceSet" {
                    # Create choices array
                    Add-ScriptLine "`$choices = @("
                    foreach ($choice in $Element.choices) {
                        Add-ScriptLine " (New-AMChoice -Title `"$($choice.title)`" -Value `"$($choice.value)`"),"
                    }
                    # Remove the trailing comma
                    $script:output = $script:output -replace ",\r\n$", "`r`n"
                    Add-ScriptLine ")"

                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.label) { $params += "-Label `"$($Element.label)`"" }
                    if ($Element.style) { $params += "-Style `"$($Element.style)`"" }
                    if ($Element.isMultiSelect -ne $false) { $params += "-IsMultiSelect `$$($Element.isMultiSelect)" }
                    $params += "-Choices `$choices"

                    Add-ScriptLine "`$$varName = New-AMChoiceSetInput $($params -join ' ')"
                }
                "Toggle" {
                    $params = @("-Id `"$($Element.id)`"")
                    if ($Element.title) { $params += "-Title `"$($Element.title)`"" }
                    if ($Element.value) { $params += "-Value `"$($Element.value)`"" }

                    Add-ScriptLine "`$$varName = New-AMToggleInput $($params -join ' ')"
                }
                default {
                    Add-ScriptLine "# Unsupported input type: $inputType"
                    return
                }
            }

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Process Action element
        function Process-Action {
            param($Action)

            $actionType = $Action.type.Replace("Action.", "")
            $varName = Get-UniqueVarName -Type $actionType -Id $Action.id

            switch ($actionType) {
                "OpenUrl" {
                    $params = @()
                    if ($Action.title) { $params += "-Title `"$($Action.title)`"" }
                    if ($Action.url) { $params += "-Url `"$($Action.url)`"" }

                    Add-ScriptLine "`$$varName = New-AMOpenUrlAction $($params -join ' ')"
                }
                "ShowCard" {
                    $params = @()
                    if ($Action.title) { $params += "-Title `"$($Action.title)`"" }

                    # Create card definition
                    Add-ScriptLine "`$detailCard = @{"
                    Add-ScriptLine " 'type' = 'AdaptiveCard'"
                    if ($Action.card.body) {
                        Add-ScriptLine " 'body' = @("
                        foreach ($bodyItem in $Action.card.body) {
                            Add-ScriptLine " @{"
                            foreach ($prop in $bodyItem.PSObject.Properties) {
                                if ($prop.Value -is [string]) {
                                    Add-ScriptLine " '$($prop.Name)' = `"$($prop.Value)`""
                                }
                                elseif ($prop.Value -is [bool]) {
                                    Add-ScriptLine " '$($prop.Name)' = `$$($prop.Value.ToString().ToLower())"
                                }
                                else {
                                    Add-ScriptLine " '$($prop.Name)' = $($prop.Value)"
                                }
                            }
                            Add-ScriptLine " }"
                        }
                        Add-ScriptLine " )"
                    }
                    if ($Action.card.'$schema') {
                        Add-ScriptLine " '`$schema' = '$($Action.card.'$schema')'"
                    }
                    if ($Action.card.padding) {
                        Add-ScriptLine " 'padding' = '$($Action.card.padding)'"
                    }
                    Add-ScriptLine "}"

                    $params += "-Card `$detailCard"
                    Add-ScriptLine "`$$varName = New-AMShowCardAction $($params -join ' ')"
                }
                "Http" {
                    $params = @()
                    if ($Action.title) { $params += "-Title `"$($Action.title)`"" }
                    if ($Action.method) { $params += "-Verb `"$($Action.method)`"" }
                    if ($Action.url) { $params += "-Url `"$($Action.url)`"" }
                    if ($Action.body) { $params += "-Body '$($Action.body)'" }

                    Add-ScriptLine "`$$varName = New-AMExecuteAction $($params -join ' ')"
                }
                "ToggleVisibility" {
                    $params = @()
                    if ($Action.title) { $params += "-Title `"$($Action.title)`"" }

                    if ($Action.targetElements) {
                        $targets = $Action.targetElements | ForEach-Object { "`"$_`"" }
                        $params += "-TargetElements @($($targets -join ', '))"
                    }

                    Add-ScriptLine "`$$varName = New-AMToggleVisibilityAction $($params -join ' ')"
                }
                default {
                    Add-ScriptLine "# Unsupported action type: $actionType"
                    return $null
                }
            }

            return $varName
        }

        # Process ActionSet element
        function Process-ActionSet {
            param($Element, [string]$ContainerId = $null)

            $varName = Get-UniqueVarName -Type "actionSet" -Id $Element.id

            $actionVars = @()
            foreach ($action in $Element.actions) {
                $actionVar = Process-Action -Action $action
                if ($actionVar) {
                    $actionVars += "`$$actionVar"
                }
            }

            $params = @()
            if ($Element.id) { $params += "-Id `"$($Element.id)`"" }
            $params += "-Actions @($($actionVars -join ', '))"

            Add-ScriptLine "`$$varName = New-AMActionSet $($params -join ' ')"

            Add-ElementToCard -VarName $varName -ContainerId $ContainerId
        }

        # Helper to add element to card or container
        function Add-ElementToCard {
            param(
                [string]$VarName,
                [string]$ContainerId = $null
            )

            $params = @("-Card `$card", "-Element `$$VarName")
            if ($ContainerId) {
                $params += "-ContainerId `"$ContainerId`""
            }

            Add-ScriptLine "Add-AMElement $($params -join ' ')"
        }
    }

    process {
        try {
            $cardObject = $Json | ConvertFrom-Json

            # Process the card
            Process-Card -CardObject $cardObject

            # Add export line
            Add-ScriptLine "`n# Export the card"
            Add-ScriptLine "`$cardJson = Export-AMCard -Card `$card"

            # Output the result
            if ($OutputPath) {
                $script:output | Out-File -FilePath $OutputPath -Force
                Write-Output "Script written to $OutputPath"
            }
            else {
                $script:output
            }
        }
        catch {
            Write-Error "Error processing JSON: $_"
        }
    }
}