///|
/// A host-native package artifact.
pub(all) enum PackageFormat {
  App
  Zip
  Dmg
  Nsis
  AppImage
} derive(Debug, Eq)

///|
pub extend PackageFormat with Eq::{not_equal, equal}

///|
pub extend PackageFormat with Debug::{to_repr}

///|
pub fn PackageFormat::parse(
  value : String,
) -> PackageFormat raise PackagePlanError {
  match value.to_lower() {
    "app" => App
    "zip" => Zip
    "dmg" => Dmg
    "nsis" => Nsis
    "appimage" => AppImage
    _ => raise UnsupportedFormat(format=value)
  }
}

///|
pub fn PackageFormat::to_string(self : PackageFormat) -> String {
  match self {
    App => "app"
    Zip => "zip"
    Dmg => "dmg"
    Nsis => "nsis"
    AppImage => "appimage"
  }
}

///|
/// The portable destination area for an additional package payload.
pub(all) enum PayloadLocation {
  Resources
  Libraries
  Executables
} derive(Debug, Eq)

///|
pub extend PayloadLocation with Eq::{not_equal, equal}

///|
pub extend PayloadLocation with Debug::{to_repr}

///|
pub fn PayloadLocation::parse(
  value : String,
) -> PayloadLocation raise PackagePlanError {
  match value.to_lower() {
    "resources" => Resources
    "libraries" => Libraries
    "executables" => Executables
    _ => raise InvalidPayload(detail="unknown payload location: " + value)
  }
}

///|
/// A file or directory copied into the package.
pub struct Payload {
  source : String
  destination : String
  location : PayloadLocation
} derive(Debug, Eq)

///|
pub extend Payload with Eq::{not_equal, equal}

///|
pub extend Payload with Debug::{to_repr}

///|
pub fn Payload::Payload(
  source : String,
  destination : String,
  location? : PayloadLocation = Resources,
) -> Payload {
  Payload::{ source, destination, location, }
}

///|
/// A file association included in native application metadata.
pub struct DocumentType {
  name : String
  extensions : Array[String]
  role : String
} derive(Debug, Eq)

///|
pub extend DocumentType with Eq::{not_equal, equal}

///|
pub extend DocumentType with Debug::{to_repr}

///|
pub fn DocumentType::DocumentType(
  name : String,
  extensions : Array[String],
  role? : String = "Viewer",
) -> DocumentType {
  DocumentType::{ name, extensions, role, }
}

///|
pub fn DocumentType::name(self : DocumentType) -> String {
  self.name
}

///|
pub fn DocumentType::extensions(self : DocumentType) -> Array[String] {
  self.extensions.copy()
}

///|
pub fn DocumentType::role(self : DocumentType) -> String {
  self.role
}

///|
/// Host-native directories for a package while it is being assembled.
///
/// The paths are valid only for the duration of `build_with`. A caller may use
/// them to add framework-specific files after the generic payloads have been
/// copied and before signing and artifact creation begin.
pub struct PackageLayout {
  root : String
  executable : String
  resources : String
  libraries : String
  executables : String
}

///|
pub fn PackageLayout::root(self : PackageLayout) -> String {
  self.root
}

///|
pub fn PackageLayout::executable(self : PackageLayout) -> String {
  self.executable
}

///|
pub fn PackageLayout::resources(self : PackageLayout) -> String {
  self.resources
}

///|
pub fn PackageLayout::libraries(self : PackageLayout) -> String {
  self.libraries
}

///|
pub fn PackageLayout::executables(self : PackageLayout) -> String {
  self.executables
}

///|
/// An additional string property written to a generated macOS Info.plist.
pub struct MacosPlistString {
  key : String
  value : String
}

///|
pub fn MacosPlistString::MacosPlistString(
  key : String,
  value : String,
) -> MacosPlistString {
  MacosPlistString::{ key, value, }
}

///|
/// A nested macOS code object signed before the main executable and app.
///
/// `path` is relative to the staged `.app` root. Array order is signing order,
/// allowing callers to describe inside-out signing for nested frameworks.
pub struct MacosSignTarget {
  path : String
  runtime_options : Bool
  entitlements : Bool
  identifier : String?
}

///|
pub fn MacosSignTarget::MacosSignTarget(
  path : String,
  runtime_options? : Bool = true,
  entitlements? : Bool = false,
  identifier? : String,
) -> MacosSignTarget {
  MacosSignTarget::{ path, runtime_options, entitlements, identifier, }
}

///|
/// Host-specific additions to the otherwise portable package specification.
pub struct PackageCustomization {
  windows_icon_embedded : Bool
  macos_plist_strings : Array[MacosPlistString]
  macos_sign_targets : Array[MacosSignTarget]
  windows_sign_targets : Array[String]
  windows_verify_targets : Array[String]
  linux_resources_path : String?
}

///|
/// Set `windows_icon_embedded` when the executable already contains its icon.
/// Package icon paths remain available without modifying the executable again.
pub fn PackageCustomization::PackageCustomization(
  windows_icon_embedded? : Bool = false,
  macos_plist_strings? : Array[MacosPlistString] = [],
  macos_sign_targets? : Array[MacosSignTarget] = [],
  windows_sign_targets? : Array[String] = [],
  windows_verify_targets? : Array[String] = [],
  linux_resources_path? : String,
) -> PackageCustomization {
  PackageCustomization::{
    windows_icon_embedded,
    macos_plist_strings,
    macos_sign_targets,
    windows_sign_targets,
    windows_verify_targets,
    linux_resources_path,
  }
}

///|
/// Windows NSIS installation scope. `Both` lets the installer user choose.
pub(all) enum NsisInstallMode {
  CurrentUser
  PerMachine
  Both
} derive(Debug, Eq)

///|
pub extend NsisInstallMode with Eq::{not_equal, equal}

///|
pub extend NsisInstallMode with Debug::{to_repr}

///|
pub fn NsisInstallMode::parse(
  value : String,
) -> NsisInstallMode raise PackagePlanError {
  match value {
    "currentUser" => CurrentUser
    "perMachine" => PerMachine
    "both" => Both
    _ => raise UnsupportedNsisInstallMode(value~)
  }
}

///|
pub fn NsisInstallMode::to_string(self : NsisInstallMode) -> String {
  match self {
    CurrentUser => "currentUser"
    PerMachine => "perMachine"
    Both => "both"
  }
}

///|
/// Complete input to the host-native packager.
/// `macos_minimum_system_version` sets the minimum supported macOS declaration.
/// Omit it to leave LSMinimumSystemVersion unset. Binary compatibility is not
/// inferred or validated by the packager.
pub struct PackageSpec {
  executable : String
  product_name : String
  identifier : String
  version : String
  formats : Array[PackageFormat]
  output : String
  payloads : Array[Payload]
  icons : Array[String]
  url_schemes : Array[String]
  document_types : Array[DocumentType]
  macos_minimum_system_version : String?
  nsis_install_mode : NsisInstallMode
  sign : Bool
  notarize : Bool
} derive(Debug, Eq)

///|
pub extend PackageSpec with Eq::{not_equal, equal}

///|
pub extend PackageSpec with Debug::{to_repr}

///|
pub fn PackageSpec::PackageSpec(
  executable : String,
  product_name : String,
  identifier : String,
  version : String,
  formats : Array[PackageFormat],
  output : String,
  payloads? : Array[Payload] = [],
  icons? : Array[String] = [],
  url_schemes? : Array[String] = [],
  document_types? : Array[DocumentType] = [],
  macos_minimum_system_version? : String,
  nsis_install_mode? : NsisInstallMode = CurrentUser,
  sign? : Bool = false,
  notarize? : Bool = false,
) -> PackageSpec {
  PackageSpec::{
    executable,
    product_name,
    identifier,
    version,
    formats,
    output,
    payloads,
    icons,
    url_schemes,
    document_types,
    macos_minimum_system_version,
    nsis_install_mode,
    sign: sign || notarize,
    notarize,
  }
}