///|
pub(all) enum CapabilityIssueSeverity {
  CapabilityWarning
  CapabilityError
} derive(Debug, Eq)

///|
pub fn CapabilityIssueSeverity::name(self : CapabilityIssueSeverity) -> String {
  match self {
    CapabilityWarning => "warning"
    CapabilityError => "error"
  }
}

///|
pub fn CapabilityIssueSeverity::is_error(
  self : CapabilityIssueSeverity,
) -> Bool {
  self is CapabilityError
}

///|
pub struct CapabilityCompileIssue {
  severity : CapabilityIssueSeverity
  code : String
  message : String
  plugin : String
  route : String
  permission : String
  capability : String
  window_label : String
} derive(Debug, Eq)

///|
fn CapabilityCompileIssue::new(
  severity~ : CapabilityIssueSeverity,
  code~ : String,
  message~ : String,
  plugin? : String = "",
  route? : String = "",
  permission? : String = "",
  capability? : String = "",
  window_label? : String = "",
) -> CapabilityCompileIssue {
  {
    severity,
    code,
    message,
    plugin,
    route,
    permission,
    capability,
    window_label,
  }
}

///|
pub fn CapabilityCompileIssue::severity(
  self : CapabilityCompileIssue,
) -> CapabilityIssueSeverity {
  self.severity
}

///|
pub fn CapabilityCompileIssue::code(self : CapabilityCompileIssue) -> String {
  self.code
}

///|
pub fn CapabilityCompileIssue::message(self : CapabilityCompileIssue) -> String {
  self.message
}

///|
pub fn CapabilityCompileIssue::plugin(self : CapabilityCompileIssue) -> String {
  self.plugin
}

///|
pub fn CapabilityCompileIssue::route(self : CapabilityCompileIssue) -> String {
  self.route
}

///|
pub fn CapabilityCompileIssue::permission(
  self : CapabilityCompileIssue,
) -> String {
  self.permission
}

///|
pub fn CapabilityCompileIssue::capability(
  self : CapabilityCompileIssue,
) -> String {
  self.capability
}

///|
pub fn CapabilityCompileIssue::window_label(
  self : CapabilityCompileIssue,
) -> String {
  self.window_label
}

///|
pub fn CapabilityCompileIssue::to_json(self : CapabilityCompileIssue) -> String {
  [
    "{",
    "\"severity\":\{self.severity.name().json_string()},",
    "\"code\":\{self.code.json_string()},",
    "\"message\":\{self.message.json_string()},",
    "\"plugin\":\{self.plugin.json_string()},",
    "\"route\":\{self.route.json_string()},",
    "\"permission\":\{self.permission.json_string()},",
    "\"capability\":\{self.capability.json_string()},",
    "\"windowLabel\":\{self.window_label.json_string()}",
    "}",
  ].join("")
}

///|
pub struct CapabilityCompileReport {
  plugin_contracts : Array[PluginContract]
  command_manifest : CommandManifest
  permission_manifest : PermissionManifest
  issues : Array[CapabilityCompileIssue]
} derive(Debug, Eq)

///|
pub fn CapabilityCompileReport::from_plugins(
  plugins~ : Array[Plugin],
  capabilities~ : Array[Capability],
  windows~ : Array[ResolvedWindow],
) -> CapabilityCompileReport {
  let command_manifest = CommandManifest::from_plugins(
    plugins~,
    capabilities~,
    windows~,
  )
  let permission_manifest = PermissionManifest::from_command_manifest(
    command_manifest,
    capabilities~,
  )
  let plugin_contracts = plugins.map(fn(plugin) { plugin.contract() })
  let issues = capability_compile_issues(
    plugin_contracts~,
    command_manifest,
    permission_manifest,
    capabilities~,
    windows~,
  )
  { plugin_contracts, command_manifest, permission_manifest, issues }
}

///|
pub fn RuntimePlan::capability_report(
  self : RuntimePlan,
) -> CapabilityCompileReport {
  CapabilityCompileReport::from_plugins(
    plugins=self.launch.plugins(),
    capabilities=self.capabilities(),
    windows=self.windows(),
  )
}

///|
pub fn CapabilityCompileReport::plugin_contracts(
  self : CapabilityCompileReport,
) -> Array[PluginContract] {
  self.plugin_contracts.copy()
}

///|
pub fn CapabilityCompileReport::command_manifest(
  self : CapabilityCompileReport,
) -> CommandManifest {
  self.command_manifest
}

///|
pub fn CapabilityCompileReport::permission_manifest(
  self : CapabilityCompileReport,
) -> PermissionManifest {
  self.permission_manifest
}

///|
pub fn CapabilityCompileReport::issues(
  self : CapabilityCompileReport,
) -> Array[CapabilityCompileIssue] {
  self.issues.copy()
}

///|
pub fn CapabilityCompileReport::errors(
  self : CapabilityCompileReport,
) -> Array[CapabilityCompileIssue] {
  self.issues.filter(fn(issue) { issue.severity().is_error() })
}

///|
pub fn CapabilityCompileReport::warnings(
  self : CapabilityCompileReport,
) -> Array[CapabilityCompileIssue] {
  self.issues.filter(fn(issue) { !issue.severity().is_error() })
}

///|
pub fn CapabilityCompileReport::ok(self : CapabilityCompileReport) -> Bool {
  self.errors().is_empty()
}

///|
pub fn CapabilityCompileReport::error_count(
  self : CapabilityCompileReport,
) -> Int {
  self.errors().length()
}

///|
pub fn CapabilityCompileReport::warning_count(
  self : CapabilityCompileReport,
) -> Int {
  self.warnings().length()
}

///|
pub fn CapabilityCompileReport::denied_routes(
  self : CapabilityCompileReport,
) -> Array[String] {
  self.permission_manifest.denied_routes()
}

///|
pub fn CapabilityCompileReport::unused_permissions(
  self : CapabilityCompileReport,
) -> Array[String] {
  let permissions : Array[String] = []
  for permission in self.permission_manifest.permissions() {
    if permission.commands().is_empty() && !permission.grants().is_empty() {
      permissions.push(permission.name())
    }
  }
  permissions
}

///|
pub fn CapabilityCompileReport::unknown_windows(
  self : CapabilityCompileReport,
) -> Array[String] {
  let windows : Array[String] = []
  for issue in self.issues {
    if issue.code() == "unknown-window" &&
      issue.window_label() != "" &&
      !windows.contains(issue.window_label()) {
      windows.push(issue.window_label())
    }
  }
  windows
}

///|
pub fn CapabilityCompileReport::to_json(
  self : CapabilityCompileReport,
) -> String {
  [
    "{",
    "\"ok\":\{self.ok().json_bool()},",
    "\"errorCount\":\{self.error_count()},",
    "\"warningCount\":\{self.warning_count()},",
    "\"pluginContracts\":[\{self.plugin_contracts.map(fn(contract) { contract.to_json() }).join(",")}],",
    "\"commandManifest\":\{self.command_manifest.to_json()},",
    "\"permissionManifest\":\{self.permission_manifest.to_json()},",
    "\"issues\":[\{self.issues.map(fn(issue) { issue.to_json() }).join(",")}]",
    "}",
  ].join("")
}

///|
fn capability_compile_issues(
  plugin_contracts~ : Array[PluginContract],
  command_manifest : CommandManifest,
  permission_manifest : PermissionManifest,
  capabilities~ : Array[Capability],
  windows~ : Array[ResolvedWindow],
) -> Array[CapabilityCompileIssue] {
  let issues : Array[CapabilityCompileIssue] = []
  for contract in plugin_contracts {
    for problem in contract.problems() {
      issues.push(
        CapabilityCompileIssue::new(
          severity=CapabilityError,
          code="plugin-invalid",
          message=problem,
          plugin=contract.name(),
        ),
      )
    }
  }
  for problem in CapabilityPolicy::new(capabilities~).validate() {
    issues.push(
      CapabilityCompileIssue::new(
        severity=CapabilityError,
        code="capability-invalid",
        message=problem,
      ),
    )
  }
  append_unknown_window_issues(issues, capabilities, windows)
  append_unscoped_operation_issues(issues, capabilities)
  append_denied_route_issues(issues, command_manifest)
  append_unused_permission_issues(issues, permission_manifest)
  issues
}

///|
fn append_unknown_window_issues(
  issues : Array[CapabilityCompileIssue],
  capabilities : Array[Capability],
  windows : Array[ResolvedWindow],
) -> Unit {
  let labels = windows.map(fn(window) { window.label() })
  for capability in capabilities {
    for window in capability.windows() {
      if !labels.contains(window) {
        issues.push(
          CapabilityCompileIssue::new(
            severity=CapabilityError,
            code="unknown-window",
            message="capability references unknown window: \{window}",
            capability=capability.name(),
            window_label=window,
          ),
        )
      }
    }
  }
}

///|
fn append_denied_route_issues(
  issues : Array[CapabilityCompileIssue],
  manifest : CommandManifest,
) -> Unit {
  for command in manifest.entries() {
    if command.allowed_windows().is_empty() {
      issues.push(
        CapabilityCompileIssue::new(
          severity=CapabilityError,
          code="permission-ungranted",
          message="command has no capability grant for any window: \{command.route()}",
          plugin=command.plugin(),
          route=command.route(),
          permission=command.permission().name(),
        ),
      )
    }
  }
}

///|
fn append_unscoped_operation_issues(
  issues : Array[CapabilityCompileIssue],
  capabilities : Array[Capability],
) -> Unit {
  for capability in capabilities {
    for permission in capability.permissions() {
      match required_operation_scope_kind(permission) {
        Some(kind) =>
          if capability.operation_scopes_by_kind(kind).is_empty() {
            issues.push(
              CapabilityCompileIssue::new(
                severity=CapabilityWarning,
                code="operation-scope-missing",
                message="permission grant has no \{kind.name()} operation scope: \{permission.name()}",
                permission=permission.name(),
                capability=capability.name(),
              ),
            )
          }
        None => ()
      }
    }
  }
}

///|
fn required_operation_scope_kind(
  permission : Permission,
) -> OperationScopeKind? {
  match permission {
    FileSystemRead | FileSystemWrite => Some(PathOperation)
    Shell => Some(ShellOperation)
    Network => Some(UrlOperation)
    _ => None
  }
}

///|
fn append_unused_permission_issues(
  issues : Array[CapabilityCompileIssue],
  manifest : PermissionManifest,
) -> Unit {
  for permission in manifest.permissions() {
    if permission.commands().is_empty() && !permission.grants().is_empty() {
      issues.push(
        CapabilityCompileIssue::new(
          severity=CapabilityWarning,
          code="permission-unused",
          message="permission is granted but no command requires it: \{permission.name()}",
          permission=permission.name(),
        ),
      )
    }
  }
}