private/Controls/Resolve-UiListSource.ps1

function Resolve-UiListSource {
    <#
    .SYNOPSIS
        Wraps the calling script's list in a threadsafe one and repoints the variable at the wrap.
    #>

    [CmdletBinding()]
    param(
        # Untyped, because any type on this parameter, [object] included, unwraps a [ref] and the Ref case needs the holder.
        [Parameter(Mandatory)]
        [AllowNull()]
        $Source,

        [switch]$NoBind,

        # Get-Variable -Scope counts frames up from here. 0 (this function) and 1 (the public builder) both hold the original as a parameter.
        # Starting at 0 repoints those two parameters and calls that a success, while the script's own variable stays on the original.
        # 2 skips both and climbs from there.
        [int]$ScopeOffset = 2
    )

    $uiDispatcher    = [System.Windows.Threading.Dispatcher]::CurrentDispatcher
    $collectionType  = Get-UiCollectionType -Obj $Source
    $collection      = $null
    $mirrorAttached  = $false
    $refWriteMissed  = $false
    $converted       = [System.Collections.Generic.List[string]]::new()

    # The mirror inserts through IList, and Collection<T> throws there for anything that isn't already a T, so a typed list gets a typed wrap and PS converts on Add.
    $newWrap = {
        param($list)
        $itemType = [object]
        $seed     = $list

        # PS hands a string to the IEnumerable seed as its characters
        if ($list -is [string]) { $seed = @($list) }
        elseif ($list -is [System.Collections.IList] -and !$list.IsFixedSize) {
            $listOfT = $list.GetType().GetInterface('System.Collections.Generic.IList`1')
            if ($listOfT) { $itemType = $listOfT.GetGenericArguments()[0] }
        }
        $wrapType = [PsUi.AsyncObservableCollection`1].MakeGenericType($itemType)
        ,$wrapType::new($seed, $uiDispatcher)
    }

    switch ($collectionType) {
        'Null' {
            $collection = [PsUi.AsyncObservableCollection[object]]::new($uiDispatcher)
            break
        }

        # Point it at this window's UI thread in case it was built for another one.
        'PsUiObservable' {
            try { $Source.UpdateDispatcher() } catch { Write-Debug "UpdateDispatcher failed: $_" }
            $collection = $Source
            break
        }

        'WpfObservable' {
            $wrapper = & $newWrap $Source
            $wrapper.AttachMirror($Source)
            $mirrorAttached = $true
            $collection     = $wrapper
            break
        }

        'Ref' {
            $inner     = $Source.Value
            $innerType = Get-UiCollectionType -Obj $inner
            if ($innerType -eq 'PsUiObservable') {
                try { $inner.UpdateDispatcher() } catch { Write-Debug "UpdateDispatcher failed: $_" }
                $collection = $inner
            }
            else {
                $innerName = if ($null -eq $inner) { 'null' } else { $inner.GetType().Name }
                try { $wrapper = & $newWrap $inner }
                catch {
                    throw "-ItemsSource takes a list. The [ref] points at a $innerName, which is not one, and PowerShell cannot read it as a sequence either."
                }
                if ($inner -is [System.Collections.IList] -and !$inner.IsReadOnly -and !$inner.IsFixedSize) {
                    $wrapper.AttachMirror($inner)
                    $mirrorAttached = $true
                }
                $collection = $wrapper

                # -NoBind promises the thing passed in keeps its value, and a [ref] is that thing.
                if (!$NoBind) {
                    # Writing to a [ref] whose variable can't hold a list throws, and unguarded that takes the whole window build down.
                    try { $Source.Value = $wrapper } catch { Write-Debug "[ref] target refused the wrap: $_" }

                    # [ArrayList]$list behind the ref converts the wrap, and a target that can't hold a list refuses it and keeps what it had.
                    $landed = $Source.Value
                    if (!([object]::ReferenceEquals($landed, $wrapper))) {
                        $landedDesc = if ($null -eq $landed) { 'which refused the wrap and still holds null' } else { "now a fresh $($landed.GetType().Name)" }
                        [void]$converted.Add("the [ref] target, $landedDesc")
                    }
                    # [ref]$state.List writes to the holder and stops there ([ref]$list writes through).
                    # AttachMirror carries an ObservableCollection's adds either way.
                    elseif (!(Test-UiRefWritesThrough -Reference $Source) -and $inner -isnot [System.Collections.Specialized.INotifyCollectionChanged]) {
                        $refWriteMissed = $true
                    }
                }
            }
            break
        }

        default {
            # PS can't read a DateTime or a PSCustomObject as a sequence, so the constructor dies talking about overloads.
            try { $wrapper = & $newWrap $Source }
            catch {
                throw "-ItemsSource takes a list. A $($Source.GetType().Name) is not one, and PowerShell cannot read it as a sequence either."
            }

            # A fixed size array seeds the wrap fine but a mirrored Add back into it would throw, so no mirror. The array keeps its old contents and stops seeing changes.
            if ($Source -is [System.Collections.IList] -and !$Source.IsReadOnly -and !$Source.IsFixedSize) {
                $wrapper.AttachMirror($Source)
                $mirrorAttached = $true
            }
            $collection = $wrapper
        }
    }

    # PsUiObservable is used as handed in, so no variable needs repointing.
    $repointed = [System.Collections.Generic.List[string]]::new()
    $needsBind = $collectionType -in 'WpfObservable', 'Other' -and
                 $null -ne $collection -and
                 !([object]::ReferenceEquals($collection, $Source))

    if ($needsBind -and !$NoBind) {
        for ($scopeIdx = $ScopeOffset; $scopeIdx -lt 50; $scopeIdx++) {
            try { $scopeVars = Get-Variable -Scope $scopeIdx -ErrorAction Stop }
            catch [System.ArgumentOutOfRangeException] { break }
            catch { Write-Debug "Variable bind scope $scopeIdx walk failed: $_"; continue }

            foreach ($psVar in $scopeVars) {
                $matched = $false
                try { $matched = [object]::ReferenceEquals($psVar.Value, $Source) }
                catch { Write-Debug "Variable bind read of '$($psVar.Name)' failed: $_"; continue }

                if ($matched) {
                    try {
                        Set-Variable -Name $psVar.Name -Value $collection -Scope $scopeIdx -Force -ErrorAction Stop

                        # Type constrained variables convert the wrap here too.
                        # If the read back fails, count it as landed, since a warning that can't be acted on is worse than none.
                        $landed = $collection
                        try { $landed = Get-Variable -Name $psVar.Name -Scope $scopeIdx -ValueOnly -ErrorAction Stop }
                        catch { Write-Debug "Variable bind read back of '$($psVar.Name)' failed: $_" }

                        if ([object]::ReferenceEquals($landed, $collection)) { [void]$repointed.Add("`$$($psVar.Name)@$scopeIdx") }
                        else {
                            $landedDesc = if ($null -eq $landed) { 'which refused the wrap and holds null' } else { "now a fresh $($landed.GetType().Name)" }
                            [void]$converted.Add("`$$($psVar.Name), $landedDesc")
                        }
                    }
                    catch { Write-Debug "Variable bind rewrite of '$($psVar.Name)' at scope $scopeIdx failed: $_" }
                }
            }
        }
        Write-Debug "Variables bound: $(if ($repointed.Count) { $repointed -join ', ' } else { 'nothing matched' })"
    }
    elseif ($needsBind) {
        Write-Debug "Variable bind skipped (-NoBind). The calling script's variable still points at the original collection."
    }

    return @{
        Collection     = $collection
        Type           = $collectionType
        MirrorAttached = $mirrorAttached
        Repointed      = $repointed
        NeedsBind      = $needsBind
        RefWriteMissed = $refWriteMissed
        Converted      = $converted
    }
}