///|
pub(all) enum ValidationSeverity {
  ValidationWarning
  ValidationError
} derive(Eq, Debug)

///|
pub(all) struct ValidationIssue {
  code : String
  message : String
  severity : ValidationSeverity
} derive(Eq, Debug)

///|
pub(all) struct EventValidationPolicy {
  require_topic : Bool
  require_correlation_id : Bool
  allow_empty_tags : Bool
  min_priority : Int
  max_priority : Int
  max_payload_length : Int
} derive(Eq, Debug)

///|
pub(all) struct EventValidation {
  event : Event
  issues : Array[ValidationIssue]
} derive(Eq, Debug)

///|
pub(all) struct ValidationBatch {
  results : Array[EventValidation]
  valid : Int
  invalid : Int
  warnings : Int
  errors : Int
} derive(Eq, Debug)

///|
pub fn EventValidationPolicy::default() -> EventValidationPolicy {
  {
    require_topic: false,
    require_correlation_id: false,
    allow_empty_tags: false,
    min_priority: 0,
    max_priority: 100,
    max_payload_length: 0,
  }
}

///|
fn validation_issue(
  code : String,
  message : String,
  severity : ValidationSeverity,
) -> ValidationIssue {
  { code, message, severity }
}

///|
fn has_duplicate_tags(tags : Array[String]) -> Bool {
  let seen : Array[String] = []
  for tag in tags {
    if seen.contains(tag) {
      return true
    }
    seen.push(tag)
  }
  false
}

///|
pub fn validate_event(
  event : Event,
  policy? : EventValidationPolicy = EventValidationPolicy::default(),
) -> EventValidation {
  let issues : Array[ValidationIssue] = []
  if event.name == "" {
    issues.push(
      validation_issue(
        "event.name.empty",
        "event name must not be empty",
        ValidationError,
      ),
    )
  }
  if policy.require_topic && event.topic == "" {
    issues.push(
      validation_issue(
        "event.topic.required",
        "topic is required",
        ValidationError,
      ),
    )
  }
  if event.topic != "" &&
    (
      validate_topic_pattern(event.topic) != PatternValid ||
      event.topic.contains("*") ||
      event.topic.contains("#")
    ) {
    issues.push(
      validation_issue(
        "event.topic.invalid",
        "topic contains empty or wildcard segments",
        ValidationError,
      ),
    )
  }
  if policy.require_correlation_id && event.correlation_id == "" {
    issues.push(
      validation_issue(
        "event.correlation.required",
        "correlation id is required",
        ValidationError,
      ),
    )
  }
  if event.priority < policy.min_priority ||
    event.priority > policy.max_priority {
    issues.push(
      validation_issue(
        "event.priority.range",
        "priority is outside the accepted range",
        ValidationError,
      ),
    )
  }
  if policy.max_payload_length > 0 &&
    event.payload.length() > policy.max_payload_length {
    issues.push(
      validation_issue(
        "event.payload.too_large",
        "payload exceeds configured length",
        ValidationError,
      ),
    )
  }
  if !policy.allow_empty_tags && event.tags.contains("") {
    issues.push(
      validation_issue(
        "event.tag.empty",
        "empty tags are not allowed",
        ValidationWarning,
      ),
    )
  }
  if has_duplicate_tags(event.tags) {
    issues.push(
      validation_issue(
        "event.tag.duplicate",
        "duplicate tags should be removed",
        ValidationWarning,
      ),
    )
  }
  { event, issues }
}

///|
pub fn EventValidation::is_valid(self : EventValidation) -> Bool {
  for issue in self.issues {
    if issue.severity == ValidationError {
      return false
    }
  }
  true
}

///|
pub fn EventValidation::error_count(self : EventValidation) -> Int {
  let mut count = 0
  for issue in self.issues {
    if issue.severity == ValidationError {
      count = count + 1
    }
  }
  count
}

///|
pub fn EventValidation::warning_count(self : EventValidation) -> Int {
  self.issues.length() - self.error_count()
}

///|
pub fn validate_events(
  events : Array[Event],
  policy? : EventValidationPolicy = EventValidationPolicy::default(),
) -> ValidationBatch {
  let results : Array[EventValidation] = []
  let mut valid = 0
  let mut invalid = 0
  let mut warnings = 0
  let mut errors = 0
  for event in events {
    let result = validate_event(event, policy~)
    if result.is_valid() {
      valid = valid + 1
    } else {
      invalid = invalid + 1
    }
    warnings = warnings + result.warning_count()
    errors = errors + result.error_count()
    results.push(result)
  }
  { results, valid, invalid, warnings, errors }
}

///|
pub fn validation_middleware(policy : EventValidationPolicy) -> MiddlewareFn {
  (event, _) => {
    let result = validate_event(event, policy~)
    if result.is_valid() {
      MiddlewareContinue(event)
    } else {
      MiddlewareHalt(
        "event validation failed with " +
        result.error_count().to_string() +
        " error(s)",
      )
    }
  }
}