# cfe-patch-method v1.1 — Generate method interceptor for 1C extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
	[Parameter(Mandatory)]
	[string]$ExtensionPath,

	[Parameter(Mandatory)]
	[string]$ModulePath,

	[Parameter(Mandatory)]
	[string]$MethodName,

	[Parameter(Mandatory)]
	[ValidateSet("Before","After","ModificationAndControl")]
	[string]$InterceptorType,

	[string]$Context = "НаСервере",

	[switch]$IsFunction
)

$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# --- Resolve extension path ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
	$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Leaf) {
	$ExtensionPath = Split-Path $ExtensionPath -Parent
}
$cfgFile = Join-Path $ExtensionPath "Configuration.xml"
if (-not (Test-Path $cfgFile)) {
	Write-Error "Configuration.xml not found in: $ExtensionPath"
	exit 1
}

# --- Read NamePrefix from Configuration.xml ---
$cfgDoc = New-Object System.Xml.XmlDocument
$cfgDoc.PreserveWhitespace = $false
$cfgDoc.Load($cfgFile)

$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgDoc.NameTable)
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")

$propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs)
$prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null }
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" }

# --- Map ModulePath to file path ---
# ModulePath formats:
#   Catalog.X.ObjectModule       -> Catalogs/X/Ext/ObjectModule.bsl
#   Catalog.X.ManagerModule      -> Catalogs/X/Ext/ManagerModule.bsl
#   Catalog.X.Form.Y             -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
#   CommonModule.X               -> CommonModules/X/Ext/Module.bsl
#   Document.X.ObjectModule      -> Documents/X/Ext/ObjectModule.bsl
#   Document.X.ManagerModule     -> Documents/X/Ext/ManagerModule.bsl
#   Document.X.Form.Y            -> Documents/X/Forms/Y/Ext/Form/Module.bsl

$typeDirMap = @{
	"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
	"CommonModule"="CommonModules"; "Report"="Reports"; "DataProcessor"="DataProcessors"
	"ExchangePlan"="ExchangePlans"; "ChartOfAccounts"="ChartsOfAccounts"
	"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
	"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
	"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
	"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
	"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
	"Catalogs"="Catalogs"; "Documents"="Documents"; "Enums"="Enums"
	"CommonModules"="CommonModules"; "Reports"="Reports"; "DataProcessors"="DataProcessors"
	"ExchangePlans"="ExchangePlans"; "ChartsOfAccounts"="ChartsOfAccounts"
	"ChartsOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
	"ChartsOfCalculationTypes"="ChartsOfCalculationTypes"
	"BusinessProcesses"="BusinessProcesses"; "Tasks"="Tasks"
	"InformationRegisters"="InformationRegisters"; "AccumulationRegisters"="AccumulationRegisters"
	"AccountingRegisters"="AccountingRegisters"; "CalculationRegisters"="CalculationRegisters"
}

$parts = $ModulePath.Split(".")
if ($parts.Count -lt 2) {
	Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module or CommonModule.Name"
	exit 1
}

$objType = $parts[0]
$objName = $parts[1]

if (-not $typeDirMap.ContainsKey($objType)) {
	Write-Error "Unknown object type: $objType"
	exit 1
}
$dirName = $typeDirMap[$objType]

$bslFile = $null
if ($objType -eq "CommonModule") {
	# CommonModule.X -> CommonModules/X/Ext/Module.bsl
	$bslFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Ext") "Module.bsl"
} elseif ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
	# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
	$formName = $parts[3]
	$bslFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext") "Form") "Module.bsl"
} elseif ($parts.Count -ge 3) {
	# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
	$moduleName = $parts[2]
	$moduleFileName = switch ($moduleName) {
		"ObjectModule"    { "ObjectModule.bsl" }
		"ManagerModule"   { "ManagerModule.bsl" }
		"RecordSetModule" { "RecordSetModule.bsl" }
		"CommandModule"   { "CommandModule.bsl" }
		default           { "$moduleName.bsl" }
	}
	$bslFile = Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) (Join-Path "Ext" $moduleFileName)
} else {
	Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name"
	exit 1
}

# --- Map InterceptorType to decorator ---
$decorator = switch ($InterceptorType) {
	"Before"                  { "&Перед" }
	"After"                   { "&После" }
	"ModificationAndControl"  { "&ИзменениеИКонтроль" }
}

# --- Map Context to annotation ---
$contextAnnotation = switch ($Context) {
	"НаСервере"              { "&НаСервере" }
	"НаКлиенте"              { "&НаКлиенте" }
	"НаСервереБезКонтекста"  { "&НаСервереБезКонтекста" }
	default                  { "&$Context" }
}

# --- Procedure name ---
$procName = "${namePrefix}${MethodName}"

# --- Generate BSL code ---
$keyword = if ($IsFunction) { "Функция" } else { "Процедура" }
$endKeyword = if ($IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }

$bodyLines = @()
switch ($InterceptorType) {
	"Before" {
		$bodyLines += "`t// TODO: код перед вызовом оригинального метода"
	}
	"After" {
		$bodyLines += "`t// TODO: код после вызова оригинального метода"
	}
	"ModificationAndControl" {
		$bodyLines += "`t// Скопируйте тело оригинального метода и внесите изменения,"
		$bodyLines += "`t// используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки"
	}
}

if ($IsFunction) {
	$bodyLines += "`t"
	$bodyLines += "`tВозврат Неопределено; // TODO: заменить на реальное возвращаемое значение"
}

$bslCode = @()
$bslCode += "$contextAnnotation"
$bslCode += "${decorator}(`"$MethodName`")"
$bslCode += "$keyword ${procName}()"
$bslCode += $bodyLines
$bslCode += "$endKeyword"

$bslText = ($bslCode -join "`r`n") + "`r`n"

# --- Check form borrowing for .Form. paths ---
if ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
	$formName = $parts[3]
	$dirName = $typeDirMap[$objType]
	$formMetaFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") "${formName}.xml"
	$formXmlFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext/Form.xml"

	if (-not (Test-Path $formMetaFile) -or -not (Test-Path $formXmlFile)) {
		Write-Host "[WARN] Form '$formName' metadata or Form.xml not found in extension."
		Write-Host "       Run /cfe-borrow first:"
		Write-Host "       /cfe-borrow -ExtensionPath $ExtensionPath -ConfigPath <ConfigPath> -Object `"$objType.$objName.Form.$formName`""
		Write-Host ""
	}
}

# --- Check if file exists and append ---
$bslDir = Split-Path $bslFile -Parent
if (-not (Test-Path $bslDir)) {
	New-Item -ItemType Directory -Path $bslDir -Force | Out-Null
}

$enc = New-Object System.Text.UTF8Encoding($true)

if (Test-Path $bslFile) {
	# Append to existing file
	$existing = [System.IO.File]::ReadAllText($bslFile, $enc)
	$separator = "`r`n"
	if ($existing -and -not $existing.EndsWith("`n")) {
		$separator = "`r`n`r`n"
	}
	$newContent = $existing + $separator + $bslText
	[System.IO.File]::WriteAllText($bslFile, $newContent, $enc)
	Write-Host "[OK] Добавлен перехватчик в существующий файл"
} else {
	[System.IO.File]::WriteAllText($bslFile, $bslText, $enc)
	Write-Host "[OK] Создан файл модуля"
}

Write-Host "     Файл:         $bslFile"
Write-Host "     Декоратор:    $decorator(`"$MethodName`")"
Write-Host "     Процедура:    ${procName}()"
Write-Host "     Контекст:     $contextAnnotation"
