///|
pub(all) struct PublishPolicy {
  require_end_list : Bool
  allow_absolute_uris : Bool
  allow_parent_traversal : Bool
  max_target_duration : Int
  max_segment_duration_ms : Int
  max_variant_bandwidth : Int
  min_variants : Int
  require_independent_segments : Bool
  require_version_at_least : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct PolicyReport {
  ok : Bool
  errors : Int
  warnings : Int
  checks : Int
  issues : Array[ValidationIssue]
} derive(Eq, @debug.Debug)

///|
pub fn PublishPolicy::vod_default() -> PublishPolicy {
  {
    require_end_list: true,
    allow_absolute_uris: true,
    allow_parent_traversal: false,
    max_target_duration: 12,
    max_segment_duration_ms: 12000,
    max_variant_bandwidth: 0,
    min_variants: 0,
    require_independent_segments: false,
    require_version_at_least: 3,
  }
}

///|
pub fn PublishPolicy::live_default() -> PublishPolicy {
  {
    require_end_list: false,
    allow_absolute_uris: true,
    allow_parent_traversal: false,
    max_target_duration: 10,
    max_segment_duration_ms: 10000,
    max_variant_bandwidth: 0,
    min_variants: 0,
    require_independent_segments: true,
    require_version_at_least: 3,
  }
}

///|
pub fn PublishPolicy::strict_master_default() -> PublishPolicy {
  {
    require_end_list: false,
    allow_absolute_uris: true,
    allow_parent_traversal: false,
    max_target_duration: 0,
    max_segment_duration_ms: 0,
    max_variant_bandwidth: 12000000,
    min_variants: 2,
    require_independent_segments: true,
    require_version_at_least: 4,
  }
}

///|
pub fn evaluate_publish_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
) -> PolicyReport {
  let issues : Array[ValidationIssue] = []
  let mut checks = 0
  checks += check_version_policy(playlist, policy, issues)
  checks += check_endlist_policy(playlist, policy, issues)
  checks += check_independent_policy(playlist, policy, issues)
  checks += check_duration_policy(playlist, policy, issues)
  checks += check_variant_policy(playlist, policy, issues)
  checks += check_uri_policy(playlist, policy, issues)
  policy_report_from_issues(checks, issues)
}

///|
pub fn format_policy_report(report : PolicyReport) -> String {
  let out = StringBuilder()
  out.write_string("policy=ok=")
  out.write_string(report.ok.to_string())
  out.write_string(", checks=")
  out.write_string(report.checks.to_string())
  out.write_string(", errors=")
  out.write_string(report.errors.to_string())
  out.write_string(", warnings=")
  out.write_string(report.warnings.to_string())
  for issue in report.issues {
    out.write_char('\n')
    out.write_string(severity_to_string(issue.severity))
    out.write_string(" ")
    out.write_string(issue.code)
    if issue.line > 0 {
      out.write_string(" line=")
      out.write_string(issue.line.to_string())
    }
    out.write_string(": ")
    out.write_string(issue.message)
  }
  out.to_string()
}

///|
pub fn policy_report_to_validation(report : PolicyReport) -> ValidationReport {
  report_from_issues(report.issues)
}

///|
fn check_version_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  if policy.require_version_at_least <= 0 {
    return 0
  }
  if playlist.version < policy.require_version_at_least {
    issues.push(
      ValidationIssue::warning(
        "policy-version-low",
        "playlist version is below policy minimum \{policy.require_version_at_least}",
      ),
    )
  }
  1
}

///|
fn check_endlist_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  if policy.require_end_list && !playlist.end_list {
    issues.push(
      ValidationIssue::error(
        "policy-endlist-required", "policy requires EXT-X-ENDLIST",
      ),
    )
  }
  1
}

///|
fn check_independent_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  if policy.require_independent_segments && !playlist.independent_segments {
    issues.push(
      ValidationIssue::warning(
        "policy-independent-segments-required", "policy expects EXT-X-INDEPENDENT-SEGMENTS",
      ),
    )
  }
  1
}

///|
fn check_duration_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  let mut checks = 0
  if policy.max_target_duration > 0 {
    checks += 1
    if playlist.target_duration > policy.max_target_duration {
      issues.push(
        ValidationIssue::warning(
          "policy-target-duration-high",
          "target duration exceeds policy maximum \{policy.max_target_duration}",
        ),
      )
    }
  }
  if policy.max_segment_duration_ms > 0 {
    checks += 1
    for segment in playlist.segments {
      if segment.duration_ms > policy.max_segment_duration_ms {
        issues.push(
          ValidationIssue::warning(
            "policy-segment-duration-high",
            "segment duration exceeds policy maximum \{millis_to_string(policy.max_segment_duration_ms)}",
            line=segment.line,
          ),
        )
      }
    }
  }
  checks
}

///|
fn check_variant_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  let mut checks = 0
  if policy.min_variants > 0 {
    checks += 1
    if playlist.variants.length() < policy.min_variants {
      issues.push(
        ValidationIssue::warning(
          "policy-variant-count-low",
          "variant count is below policy minimum \{policy.min_variants}",
        ),
      )
    }
  }
  if policy.max_variant_bandwidth > 0 {
    checks += 1
    for variant in playlist.variants {
      if variant.bandwidth > policy.max_variant_bandwidth {
        issues.push(
          ValidationIssue::warning(
            "policy-variant-bandwidth-high",
            "variant bandwidth exceeds policy maximum \{policy.max_variant_bandwidth}",
            line=variant.line,
          ),
        )
      }
    }
  }
  checks
}

///|
fn check_uri_policy(
  playlist : HlsPlaylist,
  policy : PublishPolicy,
  issues : Array[ValidationIssue],
) -> Int {
  let audit = audit_uris(playlist)
  let mut checks = 1
  if !policy.allow_absolute_uris {
    checks += 1
    if audit.absolute_uris > 0 {
      issues.push(
        ValidationIssue::error(
          "policy-absolute-uri-disallowed", "policy disallows absolute URIs",
        ),
      )
    }
  }
  if !policy.allow_parent_traversal {
    checks += 1
    if audit.parent_traversal_uris > 0 {
      issues.push(
        ValidationIssue::error(
          "policy-parent-traversal-disallowed", "policy disallows URI parent directory traversal",
        ),
      )
    }
  }
  if audit.empty_uris > 0 {
    issues.push(
      ValidationIssue::error("policy-empty-uri", "policy rejects empty URIs"),
    )
  }
  checks
}

///|
fn policy_report_from_issues(
  checks : Int,
  issues : Array[ValidationIssue],
) -> PolicyReport {
  let mut errors = 0
  let mut warnings = 0
  for issue in issues {
    match issue.severity {
      Error => errors += 1
      Warning => warnings += 1
      Info => ()
    }
  }
  { ok: errors == 0, errors, warnings, checks, issues }
}