///|
pub struct SecurityProfile {
  command_manifest : CommandManifest
  permission_manifest : PermissionManifest
  capability_policy : CapabilityPolicy
  capability_report : CapabilityCompileReport
  runtime_audit : RuntimeAudit
  registered_routes : Array[String]
  check_handlers : Bool
  platform : String
} derive(Debug, Eq)

///|
pub fn SecurityProfile::from_plan(
  plan : RuntimePlan,
  registered_routes? : Array[String] = [],
  check_handlers? : Bool = false,
) -> SecurityProfile {
  let registered_routes = security_profile_sorted_unique(registered_routes)
  let command_manifest = plan.command_manifest()
  let permission_manifest = plan.permission_manifest()
  let capability_policy = CapabilityPolicy::new(
    capabilities=plan.capabilities(),
  )
  let capability_report = plan.capability_report()
  let runtime_audit = if check_handlers {
    plan.audit_with_registered_routes(registered_routes)
  } else {
    plan.audit()
  }
  {
    command_manifest,
    permission_manifest,
    capability_policy,
    capability_report,
    runtime_audit,
    registered_routes,
    check_handlers,
    platform: plan.platform(),
  }
}

///|
pub fn RuntimePlan::security_profile(
  self : RuntimePlan,
  registered_routes? : Array[String] = [],
  check_handlers? : Bool = false,
) -> SecurityProfile {
  SecurityProfile::from_plan(self, registered_routes~, check_handlers~)
}

///|
pub fn AppManager::security_profile(self : AppManager) -> SecurityProfile {
  self
  .plan()
  .security_profile(
    registered_routes=self.registered_routes(),
    check_handlers=true,
  )
}

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

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

///|
pub fn SecurityProfile::capability_policy(
  self : SecurityProfile,
) -> CapabilityPolicy {
  self.capability_policy
}

///|
pub fn SecurityProfile::capability_report(
  self : SecurityProfile,
) -> CapabilityCompileReport {
  self.capability_report
}

///|
pub fn SecurityProfile::runtime_audit(self : SecurityProfile) -> RuntimeAudit {
  self.runtime_audit
}

///|
pub fn SecurityProfile::registered_routes(
  self : SecurityProfile,
) -> Array[String] {
  self.registered_routes.copy()
}

///|
pub fn SecurityProfile::check_handlers(self : SecurityProfile) -> Bool {
  self.check_handlers
}

///|
pub fn SecurityProfile::platform(self : SecurityProfile) -> String {
  self.platform
}

///|
pub fn SecurityProfile::ok(self : SecurityProfile) -> Bool {
  self.capability_report.ok() && self.runtime_audit.ok()
}

///|
pub fn SecurityProfile::error_count(self : SecurityProfile) -> Int {
  self.capability_report.error_count() + self.runtime_audit.error_count()
}

///|
pub fn SecurityProfile::warning_count(self : SecurityProfile) -> Int {
  self.capability_report.warning_count() + self.runtime_audit.warning_count()
}

///|
pub fn SecurityProfile::info_count(self : SecurityProfile) -> Int {
  self.runtime_audit.info_count()
}

///|
pub fn SecurityProfile::problems(self : SecurityProfile) -> Array[String] {
  let problems : Array[String] = []
  for issue in self.capability_report.errors() {
    security_profile_push_unique(problems, issue.message())
  }
  for problem in self.runtime_audit.problems() {
    security_profile_push_unique(problems, problem)
  }
  problems
}

///|
pub fn SecurityProfile::exposed_routes(self : SecurityProfile) -> Array[String] {
  self.runtime_audit.exposed_routes()
}

///|
pub fn SecurityProfile::denied_routes(self : SecurityProfile) -> Array[String] {
  self.capability_report.denied_routes()
}

///|
pub fn SecurityProfile::unused_permissions(
  self : SecurityProfile,
) -> Array[String] {
  self.capability_report.unused_permissions()
}

///|
pub fn SecurityProfile::allowed_origins(
  self : SecurityProfile,
  window_label~ : String,
  permission~ : Permission,
) -> Array[String] {
  self.capability_policy.allowed_origins(
    window_label~,
    permission~,
    platform=self.platform,
  )
}

///|
pub fn SecurityProfile::allows(
  self : SecurityProfile,
  window_label~ : String,
  permission~ : Permission,
  origin? : String = "",
) -> Bool {
  self.capability_policy.allows(
    window_label~,
    permission~,
    origin~,
    platform=self.platform,
  )
}

///|
pub fn SecurityProfile::evaluate(
  self : SecurityProfile,
  window_label~ : String,
  permission~ : Permission,
  origin? : String = "",
) -> CapabilityDecision {
  self.capability_policy.evaluate(
    window_label~,
    permission~,
    origin~,
    platform=self.platform,
  )
}

///|
pub fn SecurityProfile::allows_operation(
  self : SecurityProfile,
  window_label~ : String,
  permission~ : Permission,
  scope~ : OperationScope,
  origin? : String = "",
) -> Bool {
  self.capability_policy.allows_operation(
    window_label~,
    permission~,
    scope~,
    origin~,
    platform=self.platform,
  )
}

///|
pub fn SecurityProfile::evaluate_operation(
  self : SecurityProfile,
  window_label~ : String,
  permission~ : Permission,
  scope~ : OperationScope,
  origin? : String = "",
) -> CapabilityDecision {
  self.capability_policy.evaluate_operation(
    window_label~,
    permission~,
    scope~,
    origin~,
    platform=self.platform,
  )
}

///|
pub fn SecurityProfile::authorize_invoke(
  self : SecurityProfile,
  request : InvokeRequest,
) -> RuntimeInvokeAuthorization {
  self.authorize_request(request, operation_scope=None)
}

///|
pub fn SecurityProfile::authorize_operation(
  self : SecurityProfile,
  request : InvokeRequest,
  scope : OperationScope,
) -> RuntimeInvokeAuthorization {
  self.authorize_request(request, operation_scope=Some(scope))
}

///|
pub fn SecurityProfile::to_json(self : SecurityProfile) -> String {
  [
    "{",
    "\"ok\":\{self.ok().json_bool()},",
    "\"errorCount\":\{self.error_count()},",
    "\"warningCount\":\{self.warning_count()},",
    "\"infoCount\":\{self.info_count()},",
    "\"checkHandlers\":\{self.check_handlers.json_bool()},",
    "\"platform\":\{self.platform.json_string()},",
    "\"registeredRoutes\":[\{self.registered_routes.map(fn(route) { route.json_string() }).join(",")}],",
    "\"exposedRoutes\":[\{self.exposed_routes().map(fn(route) { route.json_string() }).join(",")}],",
    "\"problems\":[\{self.problems().map(fn(problem) { problem.json_string() }).join(",")}],",
    "\"capabilityReport\":\{self.capability_report.to_json()},",
    "\"runtimeAudit\":\{self.runtime_audit.to_json()}",
    "}",
  ].join("")
}

///|
fn SecurityProfile::authorize_request(
  self : SecurityProfile,
  request : InvokeRequest,
  operation_scope~ : OperationScope?,
) -> RuntimeInvokeAuthorization {
  let request_problems = request.validate()
  if !request_problems.is_empty() {
    return RuntimeInvokeAuthorization::invalid(
      request,
      reason=request_problems[0],
    )
  }
  let route = request.route()
  let registered = self.registered_routes.contains(route)
  match self.command_manifest.entry(route) {
    None =>
      RuntimeInvokeAuthorization::deny(
        kind=RuntimeInvokeUnknownCommand,
        request,
        platform=self.platform,
        registered~,
        reason="command is not declared: \{route}",
      )
    Some(entry) => {
      if self.check_handlers &&
        entry.mode().requires_registered_handler() &&
        !registered {
        return RuntimeInvokeAuthorization::deny(
          kind=RuntimeInvokeUnregisteredCommand,
          request,
          platform=self.platform,
          permission=entry.permission(),
          registered~,
          reason="command has no registered native handler: \{route}",
        )
      }
      let decision = match operation_scope {
        Some(scope) =>
          self.capability_policy.evaluate_operation(
            window_label=request.window_label(),
            permission=entry.permission(),
            scope~,
            origin=request.origin(),
            platform=self.platform,
          )
        None =>
          self.capability_policy.evaluate(
            window_label=request.window_label(),
            permission=entry.permission(),
            origin=request.origin(),
            platform=self.platform,
          )
      }
      if decision.allowed() {
        RuntimeInvokeAuthorization::allow(
          request,
          platform=self.platform,
          permission=entry.permission(),
          registered~,
          capability=decision.capability(),
        )
      } else {
        RuntimeInvokeAuthorization::deny(
          kind=RuntimeInvokePermissionDenied,
          request,
          platform=self.platform,
          permission=entry.permission(),
          registered~,
          reason=decision.reason(),
        )
      }
    }
  }
}

///|
fn security_profile_sorted_unique(routes : Array[String]) -> Array[String] {
  let unique : Array[String] = []
  for route in routes {
    security_profile_push_unique(unique, route)
  }
  unique.sort()
  unique
}

///|
fn security_profile_push_unique(values : Array[String], value : String) -> Unit {
  if !values.contains(value) {
    values.push(value)
  }
}