# Guard against double-initialization when both WSP.UCC-1 and WSP.UCC-2 dot-source this file.
# Prevents duplicate PnP module imports and variable re-initialization.
if ($global:SharedInitialized -eq $true) { return }
$global:SharedInitialized = $true

$ProgressPreference = "SilentlyContinue"
$WarningPreference = "SilentlyContinue"

# initialize logging vars
$log4netDLL = "log4net.dll"
$log4netConfigFile = "log4net.config"
$logPath = "$RootPath\Logs"

# Load and initialize logging
$loggingModule = Join-Path $RootPath "AnywhereNow\Scripts\Modules\Logging\Logging.psm1"
$loggingModule | Import-Module;	
if ($ignoreRestartPowerShell -eq $false)
{
	InitializeLogging $log4netDLL $log4netConfigFile $logPath
}

# initialize configuration file path
$configurationFile = "configuration.xml"
$configurationPath = Join-Path $RootPath $configurationFile

# Validate configuration file exists
if (!(Test-Path $configurationPath))
{
	Write-Host "Error: Configuration file not found at: $configurationPath" -ForegroundColor Red
	Write-Host "RootPath is: $RootPath" -ForegroundColor Yellow
	throw "Configuration file not found: $configurationPath"
}

# read the configuration file
try {
	$configurationXML = [xml](Get-Content -Path $configurationPath -Raw -Encoding UTF8)
}
catch {
	Write-Host "Error reading configuration file: $($_.Exception.Message)" -ForegroundColor Red
	throw
}

# Validate configuration structure
if ($null -eq $configurationXML.Configuration)
{
	Write-Host "Error: Invalid configuration.xml - missing <Configuration> root element" -ForegroundColor Red
	throw "Invalid configuration.xml structure"
}

# Remove the comments from xml 
$nodes = $configurationXML.Fields.ChildNodes
$comments = $nodes | Where-Object {$_.NodeType -eq "Comment"}
$comments | ForEach-Object { [void]$_.ParentNode.RemoveChild($_)}

function TrimValue($value) 
{
	if (![string]::IsNullOrEmpty($value))
	{
		$value = $value.Trim()
	}

	return $value
}

# initialize the values from the configuration file
# sitecollection url - command line parameter takes priority over config file
$scurl = $null
if (![string]::IsNullOrEmpty($paramSiteCollectionUrl)) 
{
	$scurl = $paramSiteCollectionUrl
	Write-Host "Using SiteCollectionUrl from parameter: $scurl" -ForegroundColor Cyan
}
elseif (![string]::IsNullOrEmpty($configurationXML.Configuration.SiteCollectionUrl))
{
	$scurl = $configurationXML.Configuration.SiteCollectionUrl
	Write-Host "Using SiteCollectionUrl from configuration.xml: $scurl" -ForegroundColor Cyan
}

$scurl = TrimValue $scurl
if (![string]::IsNullOrEmpty($scurl)) 
{
	$scurl = $scurl.TrimEnd('/')
}

# Validate URL format
if (![string]::IsNullOrEmpty($scurl) -and $scurl -notmatch "^https://[^/]+\.sharepoint\.com")
{
	Write-Host "Warning: SiteCollectionUrl does not appear to be a valid SharePoint Online URL: $scurl" -ForegroundColor Yellow
}

# SharePoint Online only - version is hardcoded
$SPversion = "online"

# Reconnect lookup columns
$reconnectLookupColumns = $configurationXML.Configuration.ReconnectLookupColumns
if ($reconnectLookupColumns -ne $null) 
{
	$reconnectLookupColumns = TrimValue $reconnectLookupColumns
	if ($reconnectLookupColumns.Tolower() -eq "true")
	{
		$reconnectLookupColumns = $true
	}
	else 
	{
		$reconnectLookupColumns = $false
	}
}
else 
{
	$reconnectLookupColumns = $false
}

if ($paramReconnectLookupColumns) 
{
	$reconnectLookupColumns = $true
}

# Reconnect lookup columns
# certificate app & tenant Id
$certificateAppId = $configurationXML.Configuration.CertificateAppId
$certificateTenantId = $configurationXML.Configuration.CertificateTenantId
if (![string]::IsNullOrEmpty($paramCertificateAppId) -and ![string]::IsNullOrEmpty($paramCertificateTenantId))
{
	$certificateAppId = $paramCertificateAppId
	$certificateTenantId = $paramCertificateTenantId
}

$certificateAppId = TrimValue $certificateAppId
$certificateTenantId = TrimValue $certificateTenantId

# certificate password
# Priority: 1. Command-line parameter (SecureString), 2. Global from pre-conversion, 3. Configuration.xml
$certificatePassword = $null
$certificatePasswordSecure = $null

# Check for command-line SecureString parameter first
if ($paramCertificatePassword -ne $null) {
	$certificatePasswordSecure = $paramCertificatePassword
	$certificatePassword = "[SecureString]"  # Marker to indicate we have a secure password
}
# Check for global SecureString from pre-conversion
elseif ($global:certificatePasswordSecureFromConfig -ne $null) {
	$certificatePasswordSecure = $global:certificatePasswordSecureFromConfig
	$certificatePassword = "[SecureString]"  # Marker to indicate we have a secure password
}
# Fall back to configuration.xml
else {
	$encryptedCertificatePassword = $configurationXML.Configuration.CertificatePassword.encrypted
	if ($encryptedCertificatePassword -ne $null) 
	{
		$certificatePassword = $configurationXML.Configuration.CertificatePassword.InnerText 
	}
	else
	{
		$certificatePassword = $configurationXML.Configuration.CertificatePassword 
	}
	$certificatePassword = TrimValue $certificatePassword
}

# certificate path & file
$certificatePath = $configurationXML.Configuration.CertificatePath
if (![string]::IsNullOrEmpty($paramCertificatePath))
{
	$certificatePath = $paramCertificatePath
}

$certificate = $false
if ((![string]::IsNullOrEmpty($certificatePath)) -and (Test-Path -Path $certificatePath -PathType Leaf))
{
	$certfile = Get-Item $certificatePath
	$certextension = $certfile.Extension;

	if ($certextension -eq '.pfx')
	{
		$certificate = $certfile
	}
}

# Additional vars
$directory_ListContent = $RootPath + "\AnywhereNow\CSV"
$siteDirectoryList = '/Lists/A365_UCC_Creator'
$columnPrefix = 'A365_'
$propBagTemplateInfoStampKey = "UccWebTemplate"
$propBagTemplateNameStampKey = "ucc-webtemplateid"
$packageSPOnline = $RootPath + "\AnywhereNow\Assets\anywhere365_assets-sponline.wsp"
$assetsPackageIdSPOnline = "7b6358e8-08c9-4407-bc2a-9d63be57db6a"

# ---------------------------------------------------------------------------
# UseSubSites flag
# Precedence: -UseSubSites switch parameter > configuration.xml > $false (default)
# ---------------------------------------------------------------------------
$useSubSites = $false
if ($global:paramUseSubSites -eq $true)
{
    $useSubSites = $true
    Write-Host "UseSubSites enabled via -UseSubSites parameter" -ForegroundColor Cyan
}
else
{
    $useSubSitesConfig = $configurationXML.Configuration.UseSubSites
    if (![string]::IsNullOrEmpty($useSubSitesConfig))
    {
        if ((TrimValue $useSubSitesConfig).ToLower() -eq "true")
        {
            $useSubSites = $true
            Write-Host "UseSubSites enabled via configuration.xml" -ForegroundColor Cyan
        }
    }
}

# ---------------------------------------------------------------------------
# Resolve parent site hierarchy from URL
# SharePoint Online URLs follow: https://{tenant}.sharepoint.com/{managedPath}/{siteCollName}[/{subsiteName}]
# The site collection is always the first two path segments after the host.
# A third segment is treated as a subsite name (single-level only).
# 4+ segments are rejected as unsupported nested paths.
# ---------------------------------------------------------------------------
function Resolve-SiteHierarchy {
    param([string]$Url)

    $uri = [Uri]$Url
    $segments = $uri.AbsolutePath.Trim('/').Split('/')

    # 0-2 segments = site collection root (e.g., /sites/FR)
    if ($segments.Count -le 2) {
        return @{
            SiteCollectionUrl = $Url
            SubsiteName       = $null
            IsSubsiteUrl      = $false
            Error             = $null
        }
    }

    # 3 segments = valid subsite URL (e.g., /sites/FR/test-ucc)
    if ($segments.Count -eq 3) {
        $siteCollUrl = "$($uri.Scheme)://$($uri.Host)/$($segments[0])/$($segments[1])"
        return @{
            SiteCollectionUrl = $siteCollUrl
            SubsiteName       = $segments[2]
            IsSubsiteUrl      = $true
            Error             = $null
        }
    }

    # 4+ segments = nested path, not supported
    return @{
        SiteCollectionUrl = $null
        SubsiteName       = $null
        IsSubsiteUrl      = $false
        Error             = "Nested subsite paths are not supported. URL has $($segments.Count) path segments (max 3). Only single-level subsites are supported (e.g., /sites/FR/test-ucc)."
    }
}

# Apply URL hierarchy resolution
# Validates the URL structure and corrects $scurl to the site collection root when a
# subsite path is passed (so AssetsVersion check, UCC Creator list etc. target the collection).
# Also guards against 4+ segment paths which are not supported.
$subsiteNameFromUrl = $null

if (![string]::IsNullOrEmpty($scurl))
{
    $resolvedHierarchy = Resolve-SiteHierarchy $scurl

    if ($resolvedHierarchy.Error)
    {
        Write-Host "Error: $($resolvedHierarchy.Error)" -ForegroundColor Red
        throw $resolvedHierarchy.Error
    }

    if ($resolvedHierarchy.IsSubsiteUrl)
    {
        if ($useSubSites)
        {
            # Override $scurl to point to the site collection root.
            # In subsite mode all site-level operations (AssetsVersion, UCC Creator list) must
            # target the collection root, not a subsite path.
            $scurl = $resolvedHierarchy.SiteCollectionUrl
            Write-Host "Subsite URL detected — using site collection root: $scurl" -ForegroundColor Cyan
            Write-Host "Note: In subsite mode, use -paramSiteCollectionUrl with the site collection root URL directly." -ForegroundColor Yellow
        }
        else
        {
            Write-Host "Warning: URL contains a subsite path but UseSubSites is false." -ForegroundColor Yellow
            Write-Host "The full URL will be treated as the site collection URL." -ForegroundColor Yellow
            Write-Host "Pass -UseSubSites or set <UseSubSites>true</UseSubSites> in configuration.xml to enable subsite mode." -ForegroundColor Yellow
        }
    }
}

# Derive managed path from URL (supports /sites/, /teams/, or custom managed paths).
# Falls back to 'sites' if pattern does not match.
if ($scurl -match "https://[^/]+\.sharepoint\.com/([^/]+)/")
{
    $managedPath = $matches[1]
}
else
{
    $managedPath = 'sites'
}

function CheckEnvironmentalVariables 
{
	if ([string]::IsNullOrEmpty($scurl)) 
	{
		return $false
    }

	# Interactive authentication - only need site URL
	if ($interactive -eq $true)
	{
		return $true
	}

	# Certificate authentication is required
	if (![string]::IsNullOrEmpty($certificateAppId) -and ![string]::IsNullOrEmpty($certificateTenantId) -and ![string]::IsNullOrEmpty($certificatePassword) -and ![string]::IsNullOrEmpty($certificatePath))
	{
		return $true
    }
	else 
	{
		return $false
	}
}

function Connect([string]$url, [switch]$showError) 
{
	# Interactive authentication - uses browser-based login
	if ($interactive -eq $true)
	{
		# Priority: 1. Command-line parameter, 2. Configuration.xml InteractiveClientId, 3. CertificateAppId from config
		$interactiveClientId = $paramInteractiveClientId
		if ([string]::IsNullOrEmpty($interactiveClientId))
		{
			$interactiveClientId = $configurationXML.Configuration.InteractiveClientId
		}
		if ([string]::IsNullOrEmpty($interactiveClientId))
		{
			# Fall back to CertificateAppId if available (same app can support both auth methods)
			$interactiveClientId = $certificateAppId
		}
		
		if ([string]::IsNullOrEmpty($interactiveClientId))
		{
			if ($showError)
			{
				Error "Interactive authentication requires a ClientId. Provide -paramInteractiveClientId, or configure InteractiveClientId or CertificateAppId in configuration.xml"
			}
			return $false
		}
		
		try
		{
			Connect-PnPOnline -Url $url -Interactive -ClientId $interactiveClientId
			return $true
		}
		catch 
		{
			if ($showError)
			{
				Error "$($_.Exception.Message)"
			}
			return $false
		}
	}

	# SharePoint Online with certificate authentication only
	if(![string]::IsNullOrEmpty($certificateAppId) -and ![string]::IsNullOrEmpty($certificateTenantId) -and ![string]::IsNullOrEmpty($certificatePassword))
	{
		if($certificate -eq $false){
			if ($showError)
			{
				Error "Certificate not detected with path '$certificatePath'"
			}
			
			return $false
		}
		else
		{
			# Use SecureString directly if available (from command-line or pre-conversion)
			if ($certificatePasswordSecure -ne $null)
			{
				try
				{
					Connect-PnPOnline -ClientId $certificateAppId -CertificatePath $certificate -CertificatePassword $certificatePasswordSecure -Url $url -Tenant $certificateTenantId
				}
				catch 
				{
					if ($showError)
					{
						Error "$($_.Exception.Message)"
					}

					return $false
				}
			}
			elseif ($encryptedCertificatePassword -ne $null -and $encryptedCertificatePassword.Tolower() -eq "true") 
			{
				try
				{
					Connect-PnPOnline -ClientId $certificateAppId -CertificatePath $certificate -CertificatePassword $(ConvertTo-SecureString -string $certificatePassword -force) -Url $url -Tenant $certificateTenantId
				}
				catch 
				{
					if ($showError)
					{
						Error "$($_.Exception.Message)"
					}

					return $false
				}
			}
			else
			{
				try 
				{
					Connect-PnPOnline -ClientId $certificateAppId -CertificatePath $certificate -CertificatePassword $(ConvertTo-SecureString $certificatePassword -asplaintext -force) -Url $url -Tenant $certificateTenantId
				}
				catch 
				{
					if ($showError)
					{
						Error "$($_.Exception.Message)"
					}

					return $false
				}
			}
		}
	}
	else
	{
		if ($showError)
		{
			Error "Certificate authentication is required. Please configure CertificateAppId, CertificateTenantId, CertificatePassword, and CertificatePath in configuration.xml, or use -Interactive for browser-based login."
		}
		return $false
	}

	return $true
}

function UploadAssets
{
	# Skip if Custom Script is disabled
	if ($global:SkipCustomScriptOperations -eq $true)
	{
		Warn "Skipping asset upload (Custom Script disabled). Use SPFx Portal Web Part instead."
		return
	}

	Info "Uploading assets (this can take up to 5 minutes)"

	$LocalFolderLocation = $RootPath + "\AnywhereNow\Assets\StyleLibrary"
    $path = $LocalFolderLocation.TrimEnd('\')
    $file = Get-ChildItem -Path $LocalFolderLocation -Recurse
    $i = 0;
    (dir $path -Recurse) | % {
        try 
		{
            $i++
            if ($_.GetType().Name -eq "FileInfo") 
			{
                $SPFolderName = "Style Library" + $_.DirectoryName.Substring($path.Length);
                Add-PnPFile -Path $_.FullName -Folder $SPFolderName -Checkout | Out-Null
            }          
        }
		catch 
		{ 
			Error "$($_.Exception.Message)"
		}
    }

	Info "Uploading assets done"
}

function AssetsVersionList
{
	try
	{
		Info "Adding AssetsVersion list"
        $listTitle = "AssetsVersion"
		$assetsListExists = $false
		$added = AddPnPList $listTitle -Hidden
		if ($added -eq $false)
		{
			$assetsListExists = $true
			Info "List already exists"
		}

        $importFile = "$directory_ListContent\AssetsVersion.csv"
        $data = Import-Csv $importFile
        foreach ($row in $data) 
		{
            $updateVersion = $row.Title
			Info "Setting AssetsVersion to $updateVersion"
			if ($assetsListExists)
			{
				SetPnPListItem $listTitle 1 @{"Title" = $updateVersion}
			}
			else
			{
				AddPnPListItem $listTitle @{"Title" = $updateVersion}
			}
        }
	}
    catch 
	{
        Error "$($_.Exception.Message)"
    }    
}

function FixLookup($siteId, $siteTitle, $targetListTitle, $lookupListTitle, $lookupColumnName) 
{
	try
	{
		$targetList = GetPnPList $targetListTitle
		if ($targetList -eq $null) 
		{
			Info "$siteTitle - Target list '$targetListTitle' not exists"
			return
		}
	
		$lookupList = GetPnPList $lookupListTitle
		if ($lookupList -eq $null) 
		{
			Info "$siteTitle - Lookup list '$lookupListTitle' not exists"
			return
		}

		$lookupColumn = GetPnPField $lookupColumnName $targetListTitle
		if ($lookupColumn -eq $null) 
		{
			Info "$siteTitle - Lookup column '$lookupColumnName' does not exist in list: $targetListTitle"
			return
		}

		$pos1 = $lookupColumn.SchemaXml.IndexOf("SourceID") + 11
		$pos2 = $lookupColumn.SchemaXml.IndexOf("List") + 7
		$sourceId = $lookupColumn.SchemaXml.Substring($pos1, 36)
		$listId = $lookupColumn.SchemaXml.Substring($pos2, 36)
		$lookupColumn.SchemaXml = $lookupColumn.SchemaXml.Replace($sourceId, $siteId)
		$lookupColumn.SchemaXml = $lookupColumn.SchemaXml.Replace($listId, $lookupList.Id)
		$lookupColumn.Update()
		Invoke-PnPQuery
	}
	catch 
	{
		Error "Error updating lookup column '$lookupColumnName' in list: $targetListTitle"
	}
}

function FixLookups($siteId, $siteTitle)
{
	if ($reconnectLookupColumns)
	{
		if ($siteId -eq $null) 
		{
			$web = Get-PnPWeb
			$siteId = $web.Id
			$siteTitle = $web.Title
		}
		
		Info "$siteTitle - (Re)Connecting all lookup columns"
		FixLookup $siteId $siteTitle "Endpoints" "Skills" "wsp_ucc_skill"
		FixLookup $siteId $siteTitle "Endpoints" "IVRQuestions" "wsp_ucc_endpoints_ParentQuestion"
		FixLookup $siteId $siteTitle "Holidays" "IVRQuestions" "wsp_ucc_holidays_ivrquestion"
		FixLookup $siteId $siteTitle "IDRQuestions" "IDRQuestions" "wsp_ucc_idr_parent"
		FixLookup $siteId $siteTitle "IDRQuestions" "Skills" "wsp_ucc_idr_skill"
		FixLookup $siteId $siteTitle "IVRQuestions" "IVRQuestions" "wsp_ucc_ivr_Parent"
		FixLookup $siteId $siteTitle "IVRQuestions" "Audiofiles" "wsp_ucc_ivr_AudioQuestion"
		FixLookup $siteId $siteTitle "IVRQuestions" "Audiofiles" "wsp_ucc_ivr_AudioAnswer"
		FixLookup $siteId $siteTitle "IVRQuestions" "Skills" "wsp_ucc_ivr_Skill"
		FixLookup $siteId $siteTitle "IVRQuestions" "PromptsConfig" "wsp_ucc_ivr_name"
		FixLookup $siteId $siteTitle "OutboundDialerCalls" "Skills" "wsp_ucc_odc_skill"
		FixLookup $siteId $siteTitle "OutboundDialerCalls" "Audiofiles" "wsp_ucc_odc_prewelcomeaudiomsg"
		FixLookup $siteId $siteTitle "Playlists" "Audiofiles" "wsp_ucc_pl_audiofile"
		FixLookup $siteId $siteTitle "PluginFlowSettings" "IVRQuestions" "wsp_ucc_pfs_parentquestion"
		FixLookup $siteId $siteTitle "PluginFlowSettings" "PluginAttributes" "wsp_ucc_pfs_pluginattribute"
		FixLookup $siteId $siteTitle "PluginFlowSettings" "PromptsConfig" "wsp_ucc_pfs_prompt"
		FixLookup $siteId $siteTitle "PluginFlowSettings" "Skills" "wsp_ucc_pfs_skill"
		FixLookup $siteId $siteTitle "PluginSettings" "PromptsConfig" "wsp_ucc_ps_prompt"
		FixLookup $siteId $siteTitle "PromptsConfig" "Audiofiles" "wsp_ucc_pc_audiofile"
		FixLookup $siteId $siteTitle "PromptsConfig" "IVRQuestions" "Happy"
		FixLookup $siteId $siteTitle "PromptsConfig" "IVRQuestions" "Sad"
		FixLookup $siteId $siteTitle "QualityMonitorConfig" "Audiofiles" "WelcomeAudio"
		FixLookup $siteId $siteTitle "QualityMonitorConfig" "Audiofiles" "GoodByeAudio"
		FixLookup $siteId $siteTitle "Routing" "Skills" "wsp_ucc_routing_skill"
		FixLookup $siteId $siteTitle "Routing" "IVRQuestions" "wsp_ucc_routing_parentquestion"
		FixLookup $siteId $siteTitle "Routing" "PromptsConfig" "wsp_ucc_routing_name"
		FixLookup $siteId $siteTitle "Skills" "Skills" "wsp_ucc_skills_escapeskill"
		FixLookup $siteId $siteTitle "SkillsPerAgent" "Skills" "wsp_ucc_spa_skill"
		FixLookup $siteId $siteTitle "SkillsPerAgent" "Agents" "wsp_ucc_spa_Agent"
		Info "$siteTitle - (Re)Connecting all lookup columns done"
	}
}

function GetPagesLibrary($lcid) 
{
	switch ($lcid) 
	{ 
        1030 { $pagesLibrary = "SitePages" } 
        1031 { $pagesLibrary = "SitePages" } 
        1033 { $pagesLibrary = "SitePages" } 
        1043 { $pagesLibrary = "SitePages" } 
        1036 { $pagesLibrary = "SitePages" } 
        1040 { $pagesLibrary = "SitePages" } 
        1044 { $pagesLibrary = "SitePages" }
        2070 { $pagesLibrary = "SitePages" }
        3082 { $pagesLibrary = "SitePages" }
        1053 { $pagesLibrary = "SitePages" }
        1037 { $pagesLibrary = "SitePages" }
        default { $pagesLibrary = "SitePages" }
    }

	return $pagesLibrary
}

function GetLanguage($lcid) 
{
	switch ($lcid) 
	{ 
        1030 { $language = @(1030, "da-DK", "Danish") } 
        1031 { $language = @(1031, "de-DE", "German") } 
        1033 { $language = @(1033, "en-US", "English") } 
        1043 { $language = @(1043, "nl-NL", "Dutch") } 
        1036 { $language = @(1036, "fr-FR", "French") } 
        1040 { $language = @(1040, "it-IT", "Italian") } 
        1044 { $language = @(1044, "nb-NO", "Norwegian") }
        2070 { $language = @(2070, "pt-PT", "Portuguese") }
        3082 { $language = @(3082, "es-ES", "Spanish") }
        1053 { $language = @(1053, "sv-SE", "Swedish") }
        default { $language = @(1033, "en-US", "English") }
    }

	return $language
}

function GetDocumentCTLocale($lcid)
{
	switch ($lcid) 
	{ 
        1030 { $ctName = "Dokument" } 
        1031 { $ctName = "Dokument" } 
        1033 { $ctName = "Document" } 
        1043 { $ctName = "Document" } 
        1036 { $ctName = "Document" } 
        1040 { $ctName = "Documento" } 
        1044 { $ctName = "Dokument" }
        2070 { $ctName = "Documento" }
        3082 { $ctName = "Documento" }
        1053 { $ctName = "dokument" }
        default { $ctName = "Document" }
    }

	return $ctName
}

function ConvertVersionStringToNumber($version)
{
	$versionInfo = $version.Split(".")
	$majorVersion = [int]$versioninfo[0]
	$minorVersion = [int]$versioninfo[1]
	$patchLevel = [int]$versioninfo[2]
	$rev = [int]$versioninfo[3]
	$versionNumber = "$majorVersion$minorVersion$patchLevel$rev"
	if ([int]$versionNumber -ge 60181620) 
	{
		$versionNumber = 6011
	}

	return $versionNumber
}

function SetAlternateCssUrl($cssUrl)
{
	# Skip if Custom Script is disabled
	if ($global:SkipCustomScriptOperations -eq $true)
	{
		return
	}
	$result = Set-PnPWeb -AlternateCssUrl $cssUrl
}

function AddPnPJavaScriptLink($name, $jsUrl, $sequence)
{
	# Skip if Custom Script is disabled
	if ($global:SkipCustomScriptOperations -eq $true)
	{
		return
	}

	if ($sequence -eq $null) 
	{
		$sequence = 9998
	}

	Add-PnPJavaScriptLink -Name $name -Url $jsUrl -Sequence $sequence -Scope Site
}

function RemovePnPJavaScriptLink($name)
{
	# Skip if Custom Script is disabled
	if ($global:SkipCustomScriptOperations -eq $true)
	{
		return
	}
	# SharePoint Online only
	Remove-PnPJavaScriptLink -Identity $name -Scope Site -Force
}

function SetSiteLogoUrl($logoUrl)
{
	$web = Get-PnPWeb
    $web.SiteLogoUrl = $logoUrl
    $web.Update()
    Invoke-PnPQuery
}

function UpdatePages($lcid, $updateSite, $updatePages)
{
	$pagesLocation = $RootPath + "\AnywhereNow\Pages"
	$pagesLibrary = GetPagesLibrary($lcid)
	$relativeURL = $updateSite.ServerRelativeUrl
	foreach ($updatePage in $updatePages)
	{
		# SharePoint Online only
		AddPnPFile "$pagesLocation\$updatePage" $pagesLibrary "$relativeURL/$pagesLibrary/$updatePage"
	}
}

# DEPRECATED: Classic publishing features are no longer supported per MC1117115
# This function is kept as a no-op stub for backward compatibility with update scripts
function ActivateWebPublishingFeature
{
	# NOTE: PublishingWeb feature is deprecated and not available on Communication Sites
	# This function is now a no-op. Classic publishing was removed in the modernization effort.
	Info "Skipping Web Publishing feature (deprecated per MC1117115)"
}

function EnableTeamCollaborationListsFeature
{
	$feature = Get-PnPFeature -Scope Web | Where-object {$_.DisplayName -eq "TeamCollab"}
	if ($feature -eq $null) 
	{   
		# Enable the Team Collaboration Lists feature
		Info "Enabling Team Collaboration Lists feature..."
		Enable-PnPFeature -Identity 00bfea71-4ea5-48d4-a4ad-7ea5c011abe5
		Info "Team Collaboration Lists feature activated"
	}
	else 
	{
		Info "Team Collaboration Lists feature is already active"
	} 
}

function CheckDenyAddCustomisePagesFeature
{
	# This function checks if Custom Script is enabled by trying to add/remove a JavaScript link
	# It uses raw cmdlets directly (not wrapper functions) so the check works regardless of the skip flag
	try
	{
		Add-PnPJavaScriptLink -Name "Dummy" -Url "https://anywhere365.io" -Sequence 9998 -Scope Site
		Remove-PnPJavaScriptLink -Identity "Dummy" -Scope Site -Force
	}
	catch
	{
		return $false
	}

	return $true
}

function CheckPnPPropertyBag($key)
{
	try
	{
		$property = Get-PnPPropertyBag -Key $key
		if (![string]::IsNullOrEmpty($property))
		{
			return $true
		}
	}
	catch
	{
	}

	return $false
}

function SetPnPPropertyBagValue($key, $value)
{
	try
	{
		$result = Set-PnPPropertyBagValue -Key $key -Value $value -Force
	}
	catch 
	{
	}
}

# Import PnP module for SharePoint Online
$pnpModulePath = Join-Path $RootPath "bundle\PnP.PowerShell\PnP.PowerShell.psd1"
if (Test-Path $pnpModulePath)
{
	try 
	{
		Import-Module $pnpModulePath -Force -ErrorAction Stop
		Write-Host "Info : Loaded bundled PnP.PowerShell module" -ForegroundColor Gray
	}
	catch 
	{
		Write-Host "Warning: Failed to load bundled PnP module: $($_.Exception.Message)" -ForegroundColor Yellow
		Write-Host "Info : Attempting to use system PnP.PowerShell..." -ForegroundColor Gray
		try 
		{
			Import-Module PnP.PowerShell -Force -ErrorAction Stop
			Write-Host "Info : Loaded system PnP.PowerShell module" -ForegroundColor Gray
		}
		catch 
		{
			Write-Host "Error: Could not load PnP.PowerShell module. Please install it using: Install-Module PnP.PowerShell -Force" -ForegroundColor Red
			throw "PnP.PowerShell module not available"
		}
	}
}
else
{
	Write-Host "Warning: Bundled PnP module not found at: $pnpModulePath" -ForegroundColor Yellow
	Write-Host "Info : Attempting to use system PnP.PowerShell..." -ForegroundColor Gray
	try 
	{
		Import-Module PnP.PowerShell -Force -ErrorAction Stop
		Write-Host "Info : Loaded system PnP.PowerShell module" -ForegroundColor Gray
	}
	catch 
	{
		Write-Host "Error: Could not load PnP.PowerShell module. Please install it using: Install-Module PnP.PowerShell -Force" -ForegroundColor Red
		throw "PnP.PowerShell module not available"
	}
}

try
{
	Disable-PnPPowerShellTelemetry -Force | Out-Null
}
catch
{
}
