private/New-SfMetadataModel.ps1

function New-SfMetadataModel {

    <#
        .SYNOPSIS
        Creates a metadata model skeleton, pre-initialized from the data dictionary schema.
    #>


    [CmdletBinding()]
    param (
        # Path to the directory containing the schema CSV files.
        # Defaults to the module's own static/data-dictionary-schema directory.
        [string] $SchemaPath,

        # Path to the directory containing the validation list TXT files.
        # Defaults to the module's own static/data-dictionary-validations directory.
        [string] $ValidationsPath
    )

    $ErrorActionPreference = 'Stop'
    [bool] $IsDebug   = $DebugPreference   -ne 'SilentlyContinue'
    [bool] $IsVerbose = $VerbosePreference -ne 'SilentlyContinue'

    # --- resolve default paths relative to module root
    if (-not $SchemaPath)      { $SchemaPath      = Join-Path $Script:ModuleRoot 'static/data-dictionary-schema' }
    if (-not $ValidationsPath) { $ValidationsPath = Join-Path $Script:ModuleRoot 'static/data-dictionary-validations' }

    Write-Debug "SchemaPath: $SchemaPath"
    Write-Debug "ValidationsPath: $ValidationsPath"

    # --- load schema-to-table mapping overrides from optional *.json files (filename = SchemaName).
    # --- missing/empty TableName is a silent no-op (treated as if the json did not exist);
    # --- invalid json is a terminating error.
    [hashtable] $SchemaToTableMapping = @{}

    foreach ($JsonFile in (Get-ChildItem -Path (Join-Path $SchemaPath '*') -Include '*.json')) {

        [string] $SchemaName = $JsonFile.BaseName

        try {
            $MappingDef = Get-Content -Path $JsonFile.FullName -Raw | ConvertFrom-Json
        }
        catch {
            throw "Failed to parse '$($JsonFile.FullName)': $_"
        }

        if (($MappingDef.PSObject.Properties.Name -contains 'TableName') -and
            (-not [string]::IsNullOrEmpty($MappingDef.TableName))
        ) {
            $SchemaToTableMapping[$SchemaName] = $MappingDef.TableName
        }
    }

    # --- load schema: one entry per known SchemaName; default TableName = SchemaName unless already overridden above
    [hashtable] $Schema = @{}

    foreach ($File in (Get-ChildItem -Path (Join-Path $SchemaPath '*') -Include '*.csv')) {

        [string] $SchemaName = $File.BaseName

        [array] $ColumnDefs = Import-Csv -Path $File.FullName -Header 'ColumnType', 'ContentType', 'ColumnName', 'Validation'

        [PSCustomObject[]] $Columns = foreach ($Col in $ColumnDefs) {

            [string[]] $ValidValues = $null

            if (-not [string]::IsNullOrEmpty($Col.Validation) -and $Col.Validation -ne '-') {

                [string] $ValidationFile = Join-Path $ValidationsPath "$($Col.Validation).txt"

                [string[]] $ValidValues = Get-Content -Path $ValidationFile |
                    ForEach-Object { ($_ -replace '#.*$', '').Trim() } |
                    Where-Object   { $_ -ne '' }
            }

            [PSCustomObject]@{
                ColumnType  = $Col.ColumnType
                ContentType = $Col.ContentType
                ColumnName  = $Col.ColumnName
                ValidValues = $ValidValues
            }
        }

        $Schema[$SchemaName] = $Columns

        if (-not $SchemaToTableMapping.ContainsKey($SchemaName)) {
            $SchemaToTableMapping[$SchemaName] = $SchemaName
        }
    }

    # --- initialize Tables: one empty array per known TableName (deduplicated), in alphabetical order
    [hashtable] $Tables = @{}
    foreach ($TableName in ($SchemaToTableMapping.Values | Select-Object -Unique | Sort-Object)) {
        $Tables[$TableName] = @()
    }

    # --- load static metadata types map from .csv file
    [string] $MetadataTypesPath = Join-Path $Script:ModuleRoot 'static/metadata-types.csv'
    Write-Debug "MetadataTypesPath: $MetadataTypesPath"

    [hashtable] $MetadataTypes = @{}
    if (Test-Path $MetadataTypesPath) {
        [array] $MetadataTypesList = Import-Csv $MetadataTypesPath
        foreach ($Mdt in $MetadataTypesList) {
            $MetadataTypes[$Mdt.Type] = $Mdt
        }
    } else {
        Write-Warning "NOT FOUND - MetadataTypesPath: $MetadataTypesPath"
    }

    $Model = [PSCustomObject]@{

        # --- schema: column definitions per SchemaName (validation source of truth)
        Schema               = $Schema

        # --- SchemaName -> TableName resolution (N:1, defaults to identity)
        SchemaToTableMapping = $SchemaToTableMapping

        # --- tables: imported rows per TableName (merge target, NOT per SchemaName)
        Tables               = $Tables

        # --- static metadata types reference
        MetadataTypes = $MetadataTypes

        # ################### everything below is considered legacy, to be refactored and either deleted or moved above this line ###################

        # --- metadata inventory map
        MetadataMap = @{}

        # --- settings (to be moved out in Dimension 2)
        IncludeDeprecatedTag      = $false
        ForceFormulaTreatBlanksAs = $false

        # --- package.xml definition file
        #PackageDefinition = @()

        # --- Data Dictionary format - general
        #Objects = @()
        #RecordTypes = @()
        #RecordTypePicklistValues = @()
        #Fields = @()
        #FieldDependencies = @()
        #FieldLookupFilters = @()
        #StandardValueSets = @()
        #GlobalValueSets = @()
        #PicklistValues = @()
        #StateAndCountryPicklists = @()
        #ValidationRules = @()
        #BusinessProcesses = @()
        CustomMetadatas          = @()

        # --- Data Dictionary format - Profiles / PermSets
        #Profiles = @()
        #PermissionSets = @()
        #PermissionSetGroups = @()
        #AccessesAndPermissions = @()
        #ApplicationVisibilities = @()
        #TabSettings = @()
        Roles                   = @()

        # --- raw maps (to be moved out in Dimension 2)
        AccessesAndPermissionsMap  = @{}
        ApplicationVisibilitiesMap = @{}
        TabsMap                    = @{}

        FieldPermissionsList         = @()
        LayoutAssignmentsList        = @()
        ObjectPermissionsList        = @()
        RecordTypePicklistValuesList = @()
        RecordTypeVisibilitiesList   = @()

        # --- lookup maps for data dictionary (to be moved out in Dimension 2)
        FieldTranslationsMap      = @{}
        PicklistValueTranslations = @{}

        # --- maps for matching (to be moved out in Dimension 2)
        ApplicationsMap             = @{}
        FieldsMap                   = @{}
        GlobalValueSetsMap          = @{}
        LayoutsMap                  = @{}
        ObjectsMap                  = @{}
        PagesMap                    = @{}
        RecordTypesMap              = @{}
        RecordTypePicklistValuesMap = @{}
        StandardValueSetsMap        = @{}

        # --- list fields for multi-column logic (to be moved out in Dimension 2)
        # ProfileKeys = @()
        # PermissionSetKeys = @()
        # LanguageKeys = @('de', 'en_US')

        # ################### everything above is considered legacy ###################
    }

    return $Model
}