scripts/internal/user-profile.ps1

# User Profile Management Helper
# Feature 049 Iteration 003 - FR-024, FR-025, FR-026
# Handles cross-platform user-profile.yml persistence for expertise dials

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$script:UserProfileExpertiseMap = [ordered]@{
    'architect'                     = 'software_architecture'
    'ux-ui-specialist'              = 'ui_ux'
    'product-manager'               = 'product_management'
    'ai-researcher-project-manager' = 'ai_research_project_management'
}

# Crew Interaction Profile display metadata (Feature 049 Iteration 003 + Iteration 005 / Proposal 141).
#
# The user-facing profile is a CREW INTERACTION PROFILE: four decision-area settings (1-10 or auto)
# that tell Specrew how much to ask, explain, recommend, and auto-decide. The visible DisplayLabel is
# a decision area, NOT a job title or identity the user must claim (FR-032/FR-034). The persisted
# ExpertiseKey and Specrew's internal PersonaId lens identity are STABLE internal contracts that MUST
# NOT be renamed or migrated (FR-033). Higher settings get concise, expert-level questions (the user
# decides); lower or auto settings get more explanation, recommended defaults, and transparent
# auto-decisions (FR-035). Order below is the canonical first-run + display order.
$script:CrewInteractionProfileAreas = @(
    [ordered]@{ ExpertiseKey = 'product_management';             PersonaId = 'product-manager';                DisplayLabel = 'Product Strategy';      PersonaName = 'Product Manager';                 DecisionFocus = 'Business rules, prioritization, MVP boundaries' }
    [ordered]@{ ExpertiseKey = 'ui_ux';                          PersonaId = 'ux-ui-specialist';               DisplayLabel = 'UX/UI Design';          PersonaName = 'UX/UI Specialist';                DecisionFocus = 'Interface state, accessibility, workflows' }
    [ordered]@{ ExpertiseKey = 'software_architecture';          PersonaId = 'architect';                      DisplayLabel = 'Software Architecture'; PersonaName = 'Architect';                       DecisionFocus = 'Schemas, integration boundaries, deployment' }
    [ordered]@{ ExpertiseKey = 'ai_research_project_management'; PersonaId = 'ai-researcher-project-manager';  DisplayLabel = 'AI Delivery Planning';  PersonaName = 'AI Researcher / Project Manager'; DecisionFocus = 'Capacity planning, safe parallelism, agent charters' }
)

function Get-CrewInteractionProfileAreas {
    <#
    .SYNOPSIS
    Returns the authoritative Crew Interaction Profile decision-area metadata.
    .DESCRIPTION
    Each entry exposes the user-facing DisplayLabel (a decision area, not a job title), the stable
    persisted ExpertiseKey, the internal PersonaId/PersonaName lens identity, and the DecisionFocus
    blurb. Display labels are display metadata only; persona IDs and expertise keys are stable
    internal contracts (FR-032..FR-034).
    #>

    return $script:CrewInteractionProfileAreas
}

function Get-CrewInteractionProfileLabel {
    <#
    .SYNOPSIS
    Resolves the user-facing decision-area label for a persisted expertise key or internal persona ID.
    #>

    param(
        [Parameter(Mandatory = $false)][string]$ExpertiseKey,
        [Parameter(Mandatory = $false)][string]$PersonaId
    )

    foreach ($area in $script:CrewInteractionProfileAreas) {
        if ((-not [string]::IsNullOrWhiteSpace($ExpertiseKey)) -and $area.ExpertiseKey -eq $ExpertiseKey) {
            return $area.DisplayLabel
        }
        if ((-not [string]::IsNullOrWhiteSpace($PersonaId)) -and $area.PersonaId -eq $PersonaId) {
            return $area.DisplayLabel
        }
    }

    # Unknown / future area: fall back to the supplied identifier so summaries still render.
    if (-not [string]::IsNullOrWhiteSpace($PersonaId)) { return $PersonaId }
    return $ExpertiseKey
}

function Get-CrewInteractionLevelDescriptor {
    <#
    .SYNOPSIS
    Returns the collaboration descriptor for a dial value (auto/null, 1-3, 4-6, 7-10).
    .DESCRIPTION
    Describes how Specrew collaborates at that setting, not a competency judgement: higher settings
    ask concise expert-level questions and assume the user decides; lower or auto settings explain
    more, recommend defaults, and surface transparent auto-decisions (FR-035).
    #>

    param([AllowNull()]$Value)

    if ($Value -eq 'auto' -or $null -eq $Value) {
        return 'auto (Specrew recommends defaults and explains)'
    }

    $numeric = 0
    if ([int]::TryParse([string]$Value, [ref]$numeric)) {
        if ($numeric -ge 7) { return "$Value (Senior — concise questions; you decide)" }
        if ($numeric -ge 4) { return "$Value (Standard — targeted clarifications)" }
        return "$Value (Learning — Specrew explains and auto-decides with transparency)"
    }

    return [string]$Value
}

function Test-UserProfileKey {
    param(
        [AllowNull()]
        [object]$InputObject,
        [Parameter(Mandatory = $true)]
        [string]$Key
    )

    if ($null -eq $InputObject) {
        return $false
    }

    if ($InputObject -is [System.Collections.IDictionary]) {
        return $InputObject.Contains($Key)
    }

    return $null -ne $InputObject.PSObject.Properties[$Key]
}

function Get-UserProfileValue {
    param(
        [AllowNull()]
        [object]$InputObject,
        [Parameter(Mandatory = $true)]
        [string]$Key
    )

    if (-not (Test-UserProfileKey -InputObject $InputObject -Key $Key)) {
        return $null
    }

    if ($InputObject -is [System.Collections.IDictionary]) {
        return $InputObject[$Key]
    }

    return $InputObject.$Key
}

function ConvertTo-PersistedExpertiseValue {
    param([AllowNull()]$Value)

    if ($null -eq $Value) {
        return $null
    }

    $stringValue = [string]$Value
    if ([string]::IsNullOrWhiteSpace($stringValue)) {
        return $null
    }

    if ($stringValue -eq 'auto' -or $stringValue -eq 'null') {
        return $null
    }

    if ($stringValue -match '^\d+$') {
        $numericValue = [int]$stringValue
        if ($numericValue -ge 1 -and $numericValue -le 10) {
            return $numericValue
        }
    }

    return $null
}

function ConvertTo-RuntimeExpertiseDial {
    param([AllowNull()]$Value)

    $persistedValue = ConvertTo-PersistedExpertiseValue -Value $Value
    if ($null -eq $persistedValue) {
        return 'auto'
    }

    return $persistedValue
}

function ConvertTo-NormalizedUserProfile {
    param(
        [AllowNull()]
        [object]$RawProfile
    )

    if ($null -eq $RawProfile) {
        return $null
    }

    $profile = [ordered]@{
        schema                       = '1.0'
        specrew_version_at_creation  = ''
        created_at                   = ''
        last_updated_at              = ''
        user_name                    = $null
        expertise                    = [ordered]@{
            software_architecture           = $null
            ui_ux                           = $null
            product_management              = $null
            ai_research_project_management  = $null
        }
        preferences                  = [ordered]@{
            preferred_intake_depth = 'auto'
        }
        expertise_dials              = [ordered]@{}
        schema_version               = '1.0'
        updated_at                   = ''
    }

    $schema = Get-UserProfileValue -InputObject $RawProfile -Key 'schema'
    if ($null -eq $schema) {
        $schema = Get-UserProfileValue -InputObject $RawProfile -Key 'schema_version'
    }
    if ($null -ne $schema) {
        $profile.schema = [string]$schema
    }

    foreach ($key in @('specrew_version_at_creation', 'created_at', 'user_name')) {
        $value = Get-UserProfileValue -InputObject $RawProfile -Key $key
        if ($null -ne $value) {
            $profile[$key] = $value
        }
    }

    $lastUpdated = Get-UserProfileValue -InputObject $RawProfile -Key 'last_updated_at'
    if ($null -eq $lastUpdated) {
        $lastUpdated = Get-UserProfileValue -InputObject $RawProfile -Key 'updated_at'
    }
    if ($null -ne $lastUpdated) {
        $profile.last_updated_at = $lastUpdated
    }

    $rawPreferences = Get-UserProfileValue -InputObject $RawProfile -Key 'preferences'
    if ($null -ne $rawPreferences) {
        $preferredDepth = Get-UserProfileValue -InputObject $rawPreferences -Key 'preferred_intake_depth'
        if (-not [string]::IsNullOrWhiteSpace([string]$preferredDepth)) {
            $profile.preferences.preferred_intake_depth = [string]$preferredDepth
        }
    }

    $rawExpertise = Get-UserProfileValue -InputObject $RawProfile -Key 'expertise'
    if ($null -ne $rawExpertise) {
        foreach ($field in @('software_architecture', 'ui_ux', 'product_management', 'ai_research_project_management')) {
            if (Test-UserProfileKey -InputObject $rawExpertise -Key $field) {
                $profile.expertise[$field] = ConvertTo-PersistedExpertiseValue -Value (Get-UserProfileValue -InputObject $rawExpertise -Key $field)
            }
        }
    }

    $rawLegacyDials = Get-UserProfileValue -InputObject $RawProfile -Key 'expertise_dials'
    if ($null -ne $rawLegacyDials) {
        foreach ($personaId in $script:UserProfileExpertiseMap.Keys) {
            if (Test-UserProfileKey -InputObject $rawLegacyDials -Key $personaId) {
                $profile.expertise[$script:UserProfileExpertiseMap[$personaId]] = ConvertTo-PersistedExpertiseValue -Value (Get-UserProfileValue -InputObject $rawLegacyDials -Key $personaId)
            }
        }
    }

    foreach ($personaId in $script:UserProfileExpertiseMap.Keys) {
        $field = $script:UserProfileExpertiseMap[$personaId]
        $profile.expertise_dials[$personaId] = ConvertTo-RuntimeExpertiseDial -Value $profile.expertise[$field]
    }

    $profile.schema_version = $profile.schema
    $profile.updated_at = $profile.last_updated_at

    return $profile
}

function Get-UserProfilePath {
    <#
    .SYNOPSIS
    Returns the cross-platform path to user-profile.yml
     
    .DESCRIPTION
    Windows: $env:USERPROFILE\.specrew\user-profile.yml
    Unix: ~/.specrew/user-profile.yml
    #>

    if ($IsWindows -or ($null -eq $IsWindows -and $env:OS -match 'Windows')) {
        $baseDir = $env:USERPROFILE
    }
    else {
        $baseDir = $env:HOME
    }
    
    $specrewDir = Join-Path $baseDir '.specrew'
    return Join-Path $specrewDir 'user-profile.yml'
}

function Test-UserProfileExists {
    <#
    .SYNOPSIS
    Checks if user-profile.yml exists
    #>

    $profilePath = Get-UserProfilePath
    return Test-Path -LiteralPath $profilePath -PathType Leaf
}

function Get-UserProfile {
    <#
    .SYNOPSIS
    Loads and returns the user profile from user-profile.yml
     
    .DESCRIPTION
    Returns a hashtable with schema_version, created_at, updated_at, and expertise_dials.
    Returns $null if the profile doesn't exist.
    #>

    param(
        [Parameter(Mandatory = $false)]
        [string]$ProfilePath
    )
    
    if (-not $ProfilePath) {
        $ProfilePath = Get-UserProfilePath
    }
    
    if (-not (Test-Path -LiteralPath $ProfilePath -PathType Leaf)) {
        return $null
    }
    
    try {
        $content = Get-Content -LiteralPath $ProfilePath -Raw -Encoding UTF8
        
        if (Get-Command ConvertFrom-Yaml -ErrorAction SilentlyContinue) {
            return ConvertTo-NormalizedUserProfile -RawProfile ($content | ConvertFrom-Yaml)
        }

        $rawProfile = [ordered]@{
            schema = '1.0'
            specrew_version_at_creation = ''
            created_at = ''
            last_updated_at = ''
            user_name = $null
            expertise = [ordered]@{}
            preferences = [ordered]@{}
            expertise_dials = [ordered]@{}
        }

        $section = $null
        foreach ($line in ($content -split "`r?`n")) {
            $trimmedLine = $line.Trim()

            if ([string]::IsNullOrWhiteSpace($trimmedLine) -or $trimmedLine.StartsWith('#')) {
                continue
            }

            if ($line -match '^(schema|schema_version|specrew_version_at_creation|created_at|last_updated_at|updated_at|user_name):\s*(.*)$') {
                $rawProfile[$matches[1]] = $matches[2].Trim().Trim('"', "'")
                $section = $null
                continue
            }

            if ($trimmedLine -match '^(expertise|preferences|expertise_dials):\s*$') {
                $section = $matches[1]
                continue
            }

            if ($line -match '^\s+([A-Za-z0-9_-]+):\s*(.*)$' -and $null -ne $section) {
                $key = $matches[1]
                $value = $matches[2].Trim().Trim('"', "'")
                if ([string]::IsNullOrWhiteSpace($value) -or $value -eq 'null') {
                    $rawProfile[$section][$key] = $null
                }
                else {
                    $rawProfile[$section][$key] = $value
                }
            }
        }

        return ConvertTo-NormalizedUserProfile -RawProfile $rawProfile
    }
    catch {
        Write-Warning "Failed to parse user-profile.yml: $($_.Exception.Message)"
        return $null
    }
}

function Save-UserProfile {
    <#
    .SYNOPSIS
    Saves the user profile to user-profile.yml
     
    .PARAMETER ExpertiseDials
    Hashtable with persona IDs as keys and expertise levels (1-10 or "auto") as values
     
    .PARAMETER ProfilePath
    Optional custom path for the profile file
    #>

    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$ExpertiseDials,
        
        [Parameter(Mandatory = $false)]
        [string]$ProfilePath
    )
    
    if (-not $ProfilePath) {
        $ProfilePath = Get-UserProfilePath
    }
    
    $profileDir = Split-Path $ProfilePath -Parent
    if (-not (Test-Path -LiteralPath $profileDir)) {
        $null = New-Item -Path $profileDir -ItemType Directory -Force
    }
    
    $timestamp = Get-Date -Format 'o'
    
    # Check if profile already exists to preserve created_at
    $existing = Get-UserProfile -ProfilePath $ProfilePath
    $createdAt = if ($existing -and $existing.created_at) {
        $existing.created_at
    }
    else {
        $timestamp
    }
    
    # Get module version
    $specrewVersion = '0.27.6'  # Default fallback
    try {
        $modulePath = Join-Path (Split-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) -Parent) 'Specrew.psd1'
        if (Test-Path $modulePath) {
            $moduleData = Import-PowerShellDataFile -Path $modulePath
            $specrewVersion = $moduleData.ModuleVersion
        }
    }
    catch {
        # Fallback to default version
    }
    
    $existingProfile = ConvertTo-NormalizedUserProfile -RawProfile $existing
    $preferredDepth = if ($existingProfile -and $existingProfile.preferences.preferred_intake_depth) {
        [string]$existingProfile.preferences.preferred_intake_depth
    }
    else {
        'auto'
    }
    $userName = if ($existingProfile -and -not [string]::IsNullOrWhiteSpace([string]$existingProfile.user_name)) {
        [string]$existingProfile.user_name
    }
    else {
        $null
    }

    $persistedExpertise = [ordered]@{}
    foreach ($personaId in $script:UserProfileExpertiseMap.Keys) {
        $persistedExpertise[$script:UserProfileExpertiseMap[$personaId]] = ConvertTo-PersistedExpertiseValue -Value $ExpertiseDials[$personaId]
    }

    $yamlLines = New-Object System.Collections.Generic.List[string]
    $yamlLines.Add('# Specrew User Profile (Crew Interaction Profile)') | Out-Null
    $yamlLines.Add('# Four decision-area settings telling Specrew how much to ask, explain, recommend, and auto-decide.') | Out-Null
    $yamlLines.Add('# The expertise.* keys below are stable internal contracts and are NOT renamed by display labels.') | Out-Null
    $yamlLines.Add('') | Out-Null
    $yamlLines.Add('schema: "1.0"') | Out-Null
    $yamlLines.Add(("specrew_version_at_creation: ""{0}""" -f $specrewVersion)) | Out-Null
    $yamlLines.Add(("created_at: ""{0}""" -f $createdAt)) | Out-Null
    $yamlLines.Add(("last_updated_at: ""{0}""" -f $timestamp)) | Out-Null
    if ($null -ne $userName) {
        $yamlLines.Add(("user_name: ""{0}""" -f $userName.Replace('"', '\"'))) | Out-Null
    }
    $yamlLines.Add('') | Out-Null
    $yamlLines.Add('# Expertise dials persist as 1-10 or null; null keeps the auto-decision path') | Out-Null
    $yamlLines.Add('# These settings persist across all Specrew projects for this user') | Out-Null
    $yamlLines.Add('expertise:') | Out-Null
    foreach ($field in @('software_architecture', 'ui_ux', 'product_management', 'ai_research_project_management')) {
        $value = $persistedExpertise[$field]
        $scalar = if ($null -eq $value) { 'null' } else { [string]$value }
        $yamlLines.Add((" {0}: {1}" -f $field, $scalar)) | Out-Null
    }
    $yamlLines.Add('') | Out-Null
    $yamlLines.Add('preferences:') | Out-Null
    $yamlLines.Add((" preferred_intake_depth: ""{0}""" -f $preferredDepth)) | Out-Null

    ($yamlLines -join "`n") | Set-Content -LiteralPath $ProfilePath -Encoding UTF8 -NoNewline
}

function Show-UserProfileSummary {
    <#
    .SYNOPSIS
    Displays a user-friendly summary of the current profile
     
    .DESCRIPTION
    Returns formatted text suitable for inclusion in start-context.json or start-summary.md
    #>

    param(
        [Parameter(Mandatory = $false)]
        [hashtable]$Profile
    )
    
    if (-not $Profile) {
        $Profile = Get-UserProfile
    }

    if (-not $Profile) {
        return "No Crew Interaction Profile found yet. Run ``specrew start`` to set how much you want Specrew to ask, explain, recommend, and auto-decide."
    }

    $summary = "**Crew Interaction Profile (current user):**`n"
    $summary += "_How much you want Specrew to ask, explain, recommend, and auto-decide across four decision areas. Higher settings get concise, expert-level questions (you make the call); lower or ``auto`` settings get more explanation, recommended defaults, and transparent auto-decisions. These are your collaboration settings — they do not rename Specrew's internal persona lenses._`n`n"

    # Resolve each decision area from the stable expertise keys, falling back to the legacy
    # expertise_dials layout keyed by internal persona IDs. Either way the persisted keys and persona
    # IDs are unchanged; only the visible labels are decision-area names (FR-032..FR-035).
    foreach ($area in Get-CrewInteractionProfileAreas) {
        $value = $null
        $resolved = $false
        if ((Test-UserProfileKey -InputObject $Profile -Key 'expertise') -and (Test-UserProfileKey -InputObject $Profile.expertise -Key $area.ExpertiseKey)) {
            $value = $Profile.expertise[$area.ExpertiseKey]
            $resolved = $true
        }
        elseif ((Test-UserProfileKey -InputObject $Profile -Key 'expertise_dials') -and (Test-UserProfileKey -InputObject $Profile.expertise_dials -Key $area.PersonaId)) {
            $value = $Profile.expertise_dials[$area.PersonaId]
            $resolved = $true
        }

        if ($resolved) {
            $summary += "- $($area.DisplayLabel): $(Get-CrewInteractionLevelDescriptor -Value $value)`n"
        }
    }

    $summary += "`nTo update: use ``/specrew-user-profile edit`` or ``/specrew-user-profile reset``"

    return $summary
}

function Invoke-FirstRunExpertisePrompt {
    <#
    .SYNOPSIS
    Prompts the user for expertise self-rating on first run
     
    .DESCRIPTION
    Presents a friendly interactive prompt asking for expertise ratings across all personas.
    Returns a hashtable suitable for Save-UserProfile.
    #>

    param(
        [Parameter(Mandatory = $false)]
        [switch]$NonInteractive,
        
        [Parameter(Mandatory = $false)]
        [hashtable]$DefaultDials
    )
    
    if ($NonInteractive) {
        # Return sensible defaults for non-interactive scenarios.
        $defaults = @{}
        foreach ($area in Get-CrewInteractionProfileAreas) {
            $defaults[$area.PersonaId] = 'auto'
        }
        return $defaults
    }

    Write-Host ""
    Write-Host "==================================================================" -ForegroundColor Cyan
    Write-Host " Welcome to Specrew - Crew Interaction Profile Setup" -ForegroundColor Cyan
    Write-Host "==================================================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Your Crew Interaction Profile tells Specrew how much to ask, explain," -ForegroundColor White
    Write-Host "recommend, and auto-decide for you across four decision areas. It is a" -ForegroundColor White
    Write-Host "collaboration setting, not a job title — Specrew keeps its own internal" -ForegroundColor White
    Write-Host "persona lenses regardless of what you choose. Set each area 1-10:" -ForegroundColor White
    Write-Host ""
    Write-Host " 1-3 : Learning (Specrew explains more and auto-decides with transparency)" -ForegroundColor Gray
    Write-Host " 4-6 : Standard (targeted clarifications)" -ForegroundColor Gray
    Write-Host " 7-10 : Senior (concise expert-level questions; you make the call)" -ForegroundColor Gray
    Write-Host " auto : Let Specrew recommend defaults and explain them" -ForegroundColor Gray
    Write-Host ""

    $dials = @{}

    foreach ($area in Get-CrewInteractionProfileAreas) {
        Write-Host "[$($area.DisplayLabel)]" -ForegroundColor Yellow
        Write-Host " $($area.DecisionFocus)" -ForegroundColor Gray

        $validInput = $false
        while (-not $validInput) {
            $input = Read-Host " Your setting (1-10 or 'auto')"

            if ($input -eq 'auto') {
                $dials[$area.PersonaId] = 'auto'
                $validInput = $true
            }
            elseif ($input -match '^\d+$' -and [int]$input -ge 1 -and [int]$input -le 10) {
                $dials[$area.PersonaId] = [string][int]$input
                $validInput = $true
            }
            else {
                Write-Host " Please enter a number between 1 and 10, or 'auto'" -ForegroundColor Red
            }
        }
        Write-Host ""
    }

    Write-Host "Crew Interaction Profile saved! Update it anytime with /specrew-user-profile edit" -ForegroundColor Green
    Write-Host ""

    return $dials
}

function Reset-UserProfile {
    <#
    .SYNOPSIS
    Deletes the user profile, triggering first-run on next start
    #>

    param(
        [Parameter(Mandatory = $false)]
        [string]$ProfilePath
    )
    
    if (-not $ProfilePath) {
        $ProfilePath = Get-UserProfilePath
    }
    
    if (Test-Path -LiteralPath $ProfilePath) {
        Remove-Item -LiteralPath $ProfilePath -Force
        Write-Host "Crew Interaction Profile reset. You will be prompted to set it again on next 'specrew start'." -ForegroundColor Yellow
    }
    else {
        Write-Host "No Crew Interaction Profile found." -ForegroundColor Gray
    }
}

function Edit-UserProfile {
    <#
    .SYNOPSIS
    Interactive editor for updating existing profile
    #>

    param(
        [Parameter(Mandatory = $false)]
        [string]$ProfilePath
    )
    
    if (-not $ProfilePath) {
        $ProfilePath = Get-UserProfilePath
    }
    
    $existing = Get-UserProfile -ProfilePath $ProfilePath

    if (-not $existing) {
        Write-Host "No Crew Interaction Profile found. Creating a new one..." -ForegroundColor Yellow
        $dials = Invoke-FirstRunExpertisePrompt
        Save-UserProfile -ExpertiseDials $dials -ProfilePath $ProfilePath
        return
    }

    Write-Host ""
    Write-Host "Current Crew Interaction Profile (decision-area settings):" -ForegroundColor Cyan
    Write-Host ""

    # Iterate the canonical decision-area order; show decision-area labels while keeping the stable
    # internal persona IDs as the dial keys we read and write (FR-032..FR-034).
    foreach ($area in Get-CrewInteractionProfileAreas) {
        $value = $existing.expertise_dials[$area.PersonaId]
        Write-Host " $($area.DisplayLabel) : $value" -ForegroundColor White
    }

    Write-Host ""
    Write-Host "Enter new settings (press Enter to keep current value):" -ForegroundColor Yellow
    Write-Host ""

    $newDials = @{}

    foreach ($area in Get-CrewInteractionProfileAreas) {
        $currentValue = $existing.expertise_dials[$area.PersonaId]
        $input = Read-Host " $($area.DisplayLabel) (current: $currentValue)"

        if ([string]::IsNullOrWhiteSpace($input)) {
            $newDials[$area.PersonaId] = $currentValue
        }
        elseif ($input -eq 'auto') {
            $newDials[$area.PersonaId] = 'auto'
        }
        elseif ($input -match '^\d+$' -and [int]$input -ge 1 -and [int]$input -le 10) {
            $newDials[$area.PersonaId] = [string][int]$input
        }
        else {
            Write-Host " Invalid input, keeping current value" -ForegroundColor Red
            $newDials[$area.PersonaId] = $currentValue
        }
    }

    Save-UserProfile -ExpertiseDials $newDials -ProfilePath $ProfilePath
    Write-Host ""
    Write-Host "Crew Interaction Profile updated!" -ForegroundColor Green
    Write-Host ""
}

function New-CrewInteractionProfileSessionContext {
    <#
    .SYNOPSIS
    Builds the current-user Crew Interaction Profile block for session context (start-context.json).
 
    .DESCRIPTION
    Marks the resolved profile as SOFT, current-user runtime collaboration guidance for all agents —
    explicitly NOT shared project truth (FR-038). Outside /speckit.specify the profile is soft guidance
    only; /speckit.specify is the only surface that hard-applies it (FR-040). Persisted expertise keys
    and internal persona IDs are preserved unchanged (FR-033); decision_areas is keyed by the visible
    decision-area label for display, while expertise_dials retains the stable persona IDs.
    #>

    param(
        [Parameter(Mandatory = $false)]
        [object]$Profile
    )

    if (-not $Profile) {
        $Profile = Get-UserProfile
    }

    if (-not $Profile) {
        return $null
    }

    $rawExpertise = Get-UserProfileValue -InputObject $Profile -Key 'expertise'

    $decisionAreas = [ordered]@{}
    foreach ($area in Get-CrewInteractionProfileAreas) {
        $setting = ConvertTo-RuntimeExpertiseDial -Value (Get-UserProfileValue -InputObject $rawExpertise -Key $area.ExpertiseKey)
        $decisionAreas[$area.DisplayLabel] = [ordered]@{
            expertise_key   = $area.ExpertiseKey
            persona_lens_id = $area.PersonaId
            setting         = $setting
        }
    }

    return [ordered]@{
        kind                 = 'current-user-crew-interaction-profile'
        scope                = 'current-user-runtime-guidance'
        shared_project_truth = $false
        application          = 'soft collaboration guidance for all agents; hard-applied only in /speckit.specify'
        guidance             = "Higher settings: ask concise expert-level questions and assume the current user decides. Lower or auto settings: explain more, recommend defaults, and surface transparent auto-decisions. These are the current user's collaboration settings, not Specrew's internal persona lenses."
        profile_path         = Get-UserProfilePath
        schema_version       = (Get-UserProfileValue -InputObject $Profile -Key 'schema_version')
        created_at           = (Get-UserProfileValue -InputObject $Profile -Key 'created_at')
        updated_at           = (Get-UserProfileValue -InputObject $Profile -Key 'updated_at')
        decision_areas       = $decisionAreas
        expertise_dials      = (Get-UserProfileValue -InputObject $Profile -Key 'expertise_dials')
    }
}

# Export functions when loaded as a module; allow dot-sourcing in tests.
if ($null -ne $ExecutionContext.SessionState.Module) {
    Export-ModuleMember -Function @(
        'Get-UserProfilePath',
        'Test-UserProfileExists',
        'Get-UserProfile',
        'Save-UserProfile',
        'Show-UserProfileSummary',
        'Invoke-FirstRunExpertisePrompt',
        'Reset-UserProfile',
        'Edit-UserProfile',
        'Get-CrewInteractionProfileAreas',
        'Get-CrewInteractionProfileLabel',
        'Get-CrewInteractionLevelDescriptor',
        'New-CrewInteractionProfileSessionContext'
    )
}