#region ===== Builder.ps1 (분리형: installer/install.ps1 + installer/lib.ps1 + 옵션 UI 포함) =====
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Set-Utf8Safe {
  try {
    [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
    [Console]::InputEncoding  = [System.Text.UTF8Encoding]::new()
  } catch {}
  try { $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' } catch {}
}
Set-Utf8Safe

$ROOT         = Split-Path -Parent $MyInvocation.MyCommand.Path
$DRIVERSDIR   = Join-Path $ROOT "drivers"
$INSTALLERDIR = Join-Path $ROOT "installer"
$OUTDIR       = Join-Path $ROOT "dist"
New-Item -ItemType Directory -Path $OUTDIR -Force | Out-Null

# ---- Builder Log ----
$BUILDER_LOG_DIR = Join-Path $env:ProgramData "RicohInstaller\BuilderLogs"
New-Item -ItemType Directory -Path $BUILDER_LOG_DIR -Force | Out-Null
$BUILDER_LOG = Join-Path $BUILDER_LOG_DIR ("builder_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date))

function Blog {
  param([string]$Msg,[string]$Level="INFO")
  $line = "[{0:yyyy-MM-dd HH:mm:ss}] {1}: {2}" -f (Get-Date), $Level, $Msg
  Add-Content -Path $BUILDER_LOG -Value $line -Encoding UTF8
}

#region ===== 관리자 권한 =====
function Ensure-Admin {
  $id=[Security.Principal.WindowsIdentity]::GetCurrent()
  $p =New-Object Security.Principal.WindowsPrincipal($id)
  if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
    exit
  }
}
#endregion

#region ===== INF 스캔 (Ricoh만) =====
function Read-InfText {
  param([string]$Path)
  $bytes = [IO.File]::ReadAllBytes($Path)
  if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) {
    return [Text.Encoding]::Unicode.GetString($bytes)
  }
  return [Text.Encoding]::Default.GetString($bytes)
}

function LooksLikePrinterInf {
  param([string]$Path)
  try {
    $t = Read-InfText -Path $Path
    $h = ($t -split "`r?`n" | Select-Object -First 250) -join "`n"
    return ($h -match '(?i)Class\s*=\s*Printer') -and ($h -match '(?is)\[Manufacturer\]')
  } catch { return $false }
}

function Extract-RicohModelsFromInf {
  param([string]$Path)
  $t = Read-InfText -Path $Path
  $set = New-Object 'System.Collections.Generic.HashSet[string]'
  foreach ($m in [regex]::Matches($t,'^\s*"([^"]+)"\s*=', 'Multiline')) {
    $name = $m.Groups[1].Value.Trim()
    if ($name -match '(?i)\bricoh\b') { [void]$set.Add($name) }
  }
  return ,($set | Sort-Object)
}

function Build-ModelMap {
  param([string]$DriversDir)
  if (-not (Test-Path $DriversDir)) { throw "drivers 폴더가 없습니다: $DriversDir" }

  $map = @{} # model -> infFull
  Get-ChildItem -Path $DriversDir -Recurse -Filter "*.inf" | ForEach-Object {
    $inf = $_.FullName
    if (-not (LooksLikePrinterInf -Path $inf)) { return }
    $models = Extract-RicohModelsFromInf -Path $inf
    foreach ($m in $models) {
      if (-not $map.ContainsKey($m)) { $map[$m] = $inf }
    }
  }
  return $map
}
#endregion

#region ===== (PS5.1 안전) drivers+installer를 stage로 복사 후 ZIP(Base64) =====
Add-Type -AssemblyName System.IO.Compression.FileSystem

function Zip-FoldersToBase64 {
  param(
    [Parameter(Mandatory=$true)][string[]]$Folders
  )

  foreach($f in $Folders){
    if (-not (Test-Path $f)) { throw "폴더 없음: $f" }
  }

  $stage = Join-Path $env:TEMP ("ricoh_stage_{0}" -f ([Guid]::NewGuid().ToString("N")))
  $tmpZip = Join-Path $env:TEMP ("ricoh_payload_{0}.zip" -f ([Guid]::NewGuid().ToString("N")))

  try {
    New-Item -ItemType Directory -Path $stage -Force | Out-Null
    if (Test-Path $tmpZip) { Remove-Item $tmpZip -Force }

    foreach ($folder in $Folders) {
      $srcRoot = (Resolve-Path $folder).Path
      $topName = Split-Path $srcRoot -Leaf   # drivers / installer

      Get-ChildItem -Path $srcRoot -Recurse -Force | ForEach-Object {
        $full = $_.FullName

        # srcRoot 기준 상대경로
        $rel = $full.Substring($srcRoot.Length).TrimStart('\','/')

        # stage 내부에 최상위 폴더 유지
        $rel2 = if ([string]::IsNullOrEmpty($rel)) { $topName } else { ($topName + "\" + $rel) }
        $dest = Join-Path $stage $rel2

        if ($_.PSIsContainer) {
          New-Item -ItemType Directory -Path $dest -Force | Out-Null
        } else {
          $destDir = Split-Path -Parent $dest
          New-Item -ItemType Directory -Path $destDir -Force | Out-Null
          Copy-Item -LiteralPath $full -Destination $dest -Force
        }
      }
    }

    [System.IO.Compression.ZipFile]::CreateFromDirectory(
      $stage,
      $tmpZip,
      [System.IO.Compression.CompressionLevel]::Optimal,
      $false
    )

    $bytes = [IO.File]::ReadAllBytes($tmpZip)
    return [Convert]::ToBase64String($bytes)

  } finally {
    try { Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue } catch {}
    try { Remove-Item $stage -Recurse -Force -ErrorAction SilentlyContinue } catch {}
  }
}
#endregion

#region ===== EXE 부트 스크립트 생성 (payload 풀고 installer\install.ps1 실행) =====
function Make-InstallerBootPs1 {
  param(
    [Parameter(Mandatory=$true)][string]$PayloadZipBase64,
    [Parameter(Mandatory=$true)][string]$OutPs1Path
  )

  $boot = @"
Set-StrictMode -Version Latest
`$ErrorActionPreference = "Stop"

function Set-Utf8Safe {
  try {
    [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
    [Console]::InputEncoding  = [System.Text.UTF8Encoding]::new()
  } catch {}
}
Set-Utf8Safe

# Boot log (문제 생겼을 때만 참고)
`$LOG_DIR = Join-Path ([Environment]::GetFolderPath("CommonApplicationData")) "RicohInstaller\\Logs"
try { New-Item -ItemType Directory -Path `$LOG_DIR -Force | Out-Null } catch {}
`$LOG = Join-Path `$LOG_DIR ("install_boot_{0:yyyyMMdd_HHmmss}.log" -f (Get-Date))
function Log([string]`$m,[string]`$lvl="INFO"){
  try { Add-Content -Path `$LOG -Value ("[{0:yyyy-MM-dd HH:mm:ss}] {1}: {2}" -f (Get-Date), `$lvl, `$m) -Encoding UTF8 } catch {}
}

function Ensure-Admin {
  `$id=[Security.Principal.WindowsIdentity]::GetCurrent()
  `$p=New-Object Security.Principal.WindowsPrincipal(`$id)
  if (`$p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { return }

  try {
    `$exePath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
    Log "Not admin. Relaunching elevated..." "WARN"
    Start-Process -FilePath `$exePath -ArgumentList "--elevated" -Verb RunAs
  } catch {
    Log ("Ensure-Admin failed: " + `$_.Exception.Message) "ERROR"
  }
  exit
}

function Extract-Payload([string]`$targetDir) {
  try { New-Item -ItemType Directory -Path `$targetDir -Force | Out-Null } catch {}
  `$zipBytes = [Convert]::FromBase64String(`$PAYLOAD_ZIP_B64.Trim())
  `$zipPath = Join-Path `$env:TEMP ("ricoh_payload_{0}.zip" -f ([Guid]::NewGuid().ToString("N")))
  [IO.File]::WriteAllBytes(`$zipPath, `$zipBytes)

  Add-Type -AssemblyName System.IO.Compression.FileSystem
  try {
    [System.IO.Compression.ZipFile]::ExtractToDirectory(`$zipPath, `$targetDir)
  } finally {
    Remove-Item `$zipPath -Force -ErrorAction SilentlyContinue
  }
}

`$PAYLOAD_ZIP_B64 = @'
$PayloadZipBase64
'@

Ensure-Admin
Log "===== BOOT START =====" "INFO"
Log ("Log file: " + `$LOG) "INFO"

try {
  `$work = Join-Path ([Environment]::GetFolderPath("CommonApplicationData")) "RicohInstaller\\Work"
  try { Remove-Item `$work -Recurse -Force -ErrorAction SilentlyContinue } catch {}
  Extract-Payload `$work

  `$installPs1 = Join-Path `$work "installer\\install.ps1"
  if (-not (Test-Path `$installPs1)) { throw "install.ps1 not found: `$installPs1" }

  Log ("Executing: " + `$installPs1) "INFO"
  # 고객에게 불필요한 콘솔 출력 방지
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File "`$installPs1" *> `$null

  Log "===== BOOT END (OK) =====" "INFO"
} catch {
  Log ("BOOT ERROR: " + `$_.Exception.ToString()) "ERROR"
  Log "===== BOOT END (FAIL) =====" "ERROR"
}
"@

  $utf8Bom = New-Object System.Text.UTF8Encoding($true)
  [IO.File]::WriteAllText($OutPs1Path, $boot, $utf8Bom)
}
#endregion

#region ===== PS2EXE =====
function Ensure-PS2EXE {
  if (-not (Get-Module -ListAvailable -Name ps2exe)) {
    Blog -Msg "ps2exe not found. Installing..." -Level "INFO"
    Install-Module ps2exe -Scope CurrentUser -Force -AllowClobber
  }
  Import-Module ps2exe -Force
  if (-not (Get-Command Invoke-PS2EXE -ErrorAction SilentlyContinue)) {
    throw "Invoke-PS2EXE not available (ps2exe import failed)."
  }
}

function Build-SingleExe {
  param(
    [Parameter(Mandatory=$true)][hashtable]$Config,
    [Parameter(Mandatory=$true)][string]$ExeName
  )

  Ensure-Admin

  if (-not (Test-Path $DRIVERSDIR))   { throw "drivers 폴더가 없습니다: $DRIVERSDIR" }
  if (-not (Test-Path $INSTALLERDIR)) { throw "installer 폴더가 없습니다: $INSTALLERDIR" }
  if (-not (Test-Path (Join-Path $INSTALLERDIR "install.ps1"))) { throw "installer\install.ps1 가 없습니다." }
  if (-not (Test-Path (Join-Path $INSTALLERDIR "lib.ps1")))     { throw "installer\lib.ps1 가 없습니다." }

  # config.json 생성
  $cfgPath = Join-Path $INSTALLERDIR "config.json"
  ($Config | ConvertTo-Json -Depth 8) | Out-File -FilePath $cfgPath -Encoding utf8

  Blog -Msg ("Build start: " + $ExeName) -Level "INFO"
  Blog -Msg ("Config: " + ($Config | ConvertTo-Json -Compress)) -Level "INFO"
  Blog -Msg "Zipping drivers + installer..." -Level "INFO"

  $payloadB64 = Zip-FoldersToBase64 -Folders @($DRIVERSDIR, $INSTALLERDIR)

  $tmpBootPs1 = Join-Path $env:TEMP ("installer_boot_{0}.ps1" -f ([Guid]::NewGuid().ToString("N")))
  Make-InstallerBootPs1 -PayloadZipBase64 $payloadB64 -OutPs1Path $tmpBootPs1

  Ensure-PS2EXE

  $outExe = Join-Path $OUTDIR $ExeName
  if (-not $outExe.ToLower().EndsWith(".exe")) { $outExe += ".exe" }

  Blog -Msg ("Invoke-PS2EXE: " + $outExe) -Level "INFO"
  Invoke-PS2EXE -InputFile $tmpBootPs1 -OutputFile $outExe -NoConsole -RequireAdmin -Title $Config.PrinterName

  Remove-Item $tmpBootPs1 -Force -ErrorAction SilentlyContinue
  Blog -Msg ("Build done: " + $outExe) -Level "INFO"
  return $outExe
}
#endregion

#region ===== UI =====
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object System.Windows.Forms.Form
$form.Text = "Ricoh EXE Builder (옵션: 컬러/흑백, 양면해제)"
$form.Size = New-Object System.Drawing.Size(860, 520)
$form.StartPosition = "CenterScreen"

$lblName = New-Object System.Windows.Forms.Label
$lblName.Text = "이름(프린터명 + EXE 파일명):"
$lblName.AutoSize = $true
$lblName.Location = New-Object System.Drawing.Point(20, 20)

$txtName = New-Object System.Windows.Forms.TextBox
$txtName.Location = New-Object System.Drawing.Point(260, 16)
$txtName.Size = New-Object System.Drawing.Size(560, 24)
$txtName.Text = "RICOH_IM_C2000"

$lblIP = New-Object System.Windows.Forms.Label
$lblIP.Text = "프린터 IP:"
$lblIP.AutoSize = $true
$lblIP.Location = New-Object System.Drawing.Point(20, 56)

$txtIP = New-Object System.Windows.Forms.TextBox
$txtIP.Location = New-Object System.Drawing.Point(260, 52)
$txtIP.Size = New-Object System.Drawing.Size(200, 24)
$txtIP.Text = "192.168.0.10"

$lblModel = New-Object System.Windows.Forms.Label
$lblModel.Text = "모델 선택(RICOH):"
$lblModel.AutoSize = $true
$lblModel.Location = New-Object System.Drawing.Point(20, 92)

$cmbModel = New-Object System.Windows.Forms.ComboBox
$cmbModel.Location = New-Object System.Drawing.Point(260, 88)
$cmbModel.Size = New-Object System.Drawing.Size(560, 24)
$cmbModel.DropDownStyle = "DropDownList"

# ---- 옵션 그룹: 컬러/흑백 ----
$grpColor = New-Object System.Windows.Forms.GroupBox
$grpColor.Text = "색상"
$grpColor.Location = New-Object System.Drawing.Point(20, 130)
$grpColor.Size = New-Object System.Drawing.Size(220, 60)

$rbColor = New-Object System.Windows.Forms.RadioButton
$rbColor.Text = "컬러"
$rbColor.Location = New-Object System.Drawing.Point(10, 25)
$rbColor.Checked = $false   # <- 여기

$rbMono = New-Object System.Windows.Forms.RadioButton
$rbMono.Text = "흑백"
$rbMono.Location = New-Object System.Drawing.Point(110, 25)
$rbMono.Checked = $true     # <- 여기 추가


$grpColor.Controls.AddRange(@($rbColor, $rbMono))

# ---- 옵션 그룹: 양면 ----
$grpDuplex = New-Object System.Windows.Forms.GroupBox
$grpDuplex.Text = "양면"
$grpDuplex.Location = New-Object System.Drawing.Point(260, 130)
$grpDuplex.Size = New-Object System.Drawing.Size(300, 60)

$rbDuplexOff = New-Object System.Windows.Forms.RadioButton
$rbDuplexOff.Text = "양면 해제"
$rbDuplexOff.Location = New-Object System.Drawing.Point(10, 25)
$rbDuplexOff.Checked = $true

$rbDuplexOn = New-Object System.Windows.Forms.RadioButton
$rbDuplexOn.Text = "양면(긴쪽)"
$rbDuplexOn.Location = New-Object System.Drawing.Point(150, 25)

$grpDuplex.Controls.AddRange(@($rbDuplexOff, $rbDuplexOn))

# ---- 버튼들 (옵션 아래로 내림) ----
$btnLoad = New-Object System.Windows.Forms.Button
$btnLoad.Text = "모델 불러오기"
$btnLoad.Location = New-Object System.Drawing.Point(260, 205)
$btnLoad.Size = New-Object System.Drawing.Size(140, 34)

$btnBuild = New-Object System.Windows.Forms.Button
$btnBuild.Text = "EXE 만들기"
$btnBuild.Location = New-Object System.Drawing.Point(410, 205)
$btnBuild.Size = New-Object System.Drawing.Size(140, 34)

$btnOpenOut = New-Object System.Windows.Forms.Button
$btnOpenOut.Text = "dist 폴더 열기"
$btnOpenOut.Location = New-Object System.Drawing.Point(560, 205)
$btnOpenOut.Size = New-Object System.Drawing.Size(140, 34)

$txtLog = New-Object System.Windows.Forms.TextBox
$txtLog.Multiline = $true
$txtLog.ScrollBars = "Vertical"
$txtLog.ReadOnly = $true
$txtLog.Location = New-Object System.Drawing.Point(20, 260)
$txtLog.Size = New-Object System.Drawing.Size(800, 200)
$txtLog.Font = New-Object System.Drawing.Font("Consolas", 9)

function UiLog { param([string]$s) $txtLog.AppendText($s + [Environment]::NewLine) }

$script:ModelMap = @{}

$btnLoad.Add_Click({
  try {
    UiLog "INF 스캔 중... (drivers 폴더)"
    Blog -Msg "Scan models..." -Level "INFO"

    $map = Build-ModelMap -DriversDir $DRIVERSDIR
    $script:ModelMap = $map

    $cmbModel.Items.Clear()
    foreach ($k in ($map.Keys | Sort-Object)) { [void]$cmbModel.Items.Add($k) }

    if ($cmbModel.Items.Count -gt 0) {
      $cmbModel.SelectedIndex = 0
      UiLog ("모델 로드 완료: " + $cmbModel.Items.Count + "개")
    } else {
      UiLog "RICOH 모델을 찾지 못했습니다."
    }
  } catch {
    UiLog ("모델 로드 실패: " + $_.Exception.Message)
    Blog -Msg ("Load failed: " + $_.Exception.Message) -Level "ERROR"
    try { UiLog $_.InvocationInfo.PositionMessage } catch {}
  }
})

$btnBuild.Add_Click({
  try {
    $pname = $txtName.Text.Trim()
    $ip    = $txtIP.Text.Trim()
    $model = if ($cmbModel.SelectedItem) { [string]$cmbModel.SelectedItem } else { "" }

    if ([string]::IsNullOrWhiteSpace($pname)) { throw "이름을 입력하세요." }
    if ([string]::IsNullOrWhiteSpace($ip))    { throw "IP를 입력하세요." }
    if ([string]::IsNullOrWhiteSpace($model)) { throw "모델을 선택하세요(먼저 모델 불러오기)." }

    if (-not $script:ModelMap.ContainsKey($model)) { throw "선택 모델의 INF를 찾지 못했습니다." }
    $infFull = $script:ModelMap[$model]

    # INF 상대경로 (payload 내부 Work 기준: drivers\... )
    $infRel = $infFull.Substring($DRIVERSDIR.Length).TrimStart('\','/')
    $infRel = ("drivers\" + $infRel).Replace('/','\')

    $colorMode = if ($rbMono.Checked) { "Mono" } else { "Color" }
    $duplex    = if ($rbDuplexOff.Checked) { "OneSided" } else { "TwoSidedLongEdge" }

    $cfg = @{
      PrinterName = $pname
      IP          = $ip
      Model       = $model
      InfRelPath  = $infRel

      ColorMode   = $colorMode
      Duplex      = $duplex
    }

    $exeName = "$pname.exe"

    UiLog ("빌드 시작: " + $exeName)
    Blog -Msg ("Config: " + ($cfg | ConvertTo-Json -Compress)) -Level "INFO"

    $outExe = Build-SingleExe -Config $cfg -ExeName $exeName

    UiLog ("✅ EXE 생성 완료: " + $outExe)
    UiLog "다른 PC에서는 이 EXE 하나만 실행하면 자동 설치됩니다."
    UiLog "빌더 로그: " + $BUILDER_LOG

  } catch {
    UiLog ("❌ EXE 생성 실패: " + $_.Exception.Message)
    try {
      UiLog "---- 위치 ----"
      UiLog $_.InvocationInfo.PositionMessage
      UiLog "---- 전체 ----"
      UiLog ($_ | Out-String)
    } catch {}
    Blog -Msg ("Build failed: " + ($_ | Out-String)) -Level "ERROR"
  }
})

$btnOpenOut.Add_Click({
  try { Start-Process explorer.exe $OUTDIR } catch {}
})

$form.Controls.AddRange(@(
  $lblName,$txtName,
  $lblIP,$txtIP,
  $lblModel,$cmbModel,
  $grpColor,$grpDuplex,
  $btnLoad,$btnBuild,$btnOpenOut,
  $txtLog
))

UiLog "준비:"
UiLog " - drivers 폴더에 리코 드라이버 패키지 전체"
UiLog " - installer 폴더에 install.ps1 + lib.ps1"
UiLog "1) '모델 불러오기' → 2) 모델 선택 → 3) IP/이름 입력 → 4) 옵션 선택 → 5) EXE 만들기"
UiLog ("생성 위치: " + $OUTDIR)
UiLog ("빌더 로그: " + $BUILDER_LOG)

$form.ShowDialog()
#endregion
#endregion
