///|
pub(all) enum CacheTier {
  CacheStable
  CacheRefreshable
  CacheTransient
  CacheCallerManaged
} derive(Eq)

///|
pub(all) struct CachePlan {
  path : String
  current : CachePolicy
  recommended : CachePolicy
  tier : CacheTier
  aligned : Bool
  reason : String
} derive(Eq)

///|
pub(all) struct PolicyProfile {
  total : Int
  immutable : Int
  revalidate : Int
  no_store : Int
  runtime : Int
  aligned : Int
  misaligned : Int
  stability_score : Int
  immutable_bytes : Int
  revalidate_bytes : Int
  no_store_bytes : Int
  runtime_bytes : Int
} derive(Eq)

///|
pub fn CacheTier::to_wire(self : CacheTier) -> String {
  match self {
    CacheStable => "stable"
    CacheRefreshable => "refreshable"
    CacheTransient => "transient"
    CacheCallerManaged => "caller-managed"
  }
}

///|
pub fn cache_tier(policy : CachePolicy) -> CacheTier {
  match policy {
    Immutable => CacheStable
    Revalidate => CacheRefreshable
    NoStore => CacheTransient
    Runtime => CacheCallerManaged
  }
}

///|
pub fn policy_profile(manifest : Manifest) -> PolicyProfile {
  let plans = cache_plans(manifest)
  let mut immutable = 0
  let mut revalidate = 0
  let mut no_store = 0
  let mut runtime = 0
  let mut aligned = 0
  let mut misaligned = 0
  let mut immutable_bytes = 0
  let mut revalidate_bytes = 0
  let mut no_store_bytes = 0
  let mut runtime_bytes = 0
  for entry in manifest.entries {
    match entry.policy {
      Immutable => {
        immutable = immutable + 1
        immutable_bytes = immutable_bytes + entry.bytes
      }
      Revalidate => {
        revalidate = revalidate + 1
        revalidate_bytes = revalidate_bytes + entry.bytes
      }
      NoStore => {
        no_store = no_store + 1
        no_store_bytes = no_store_bytes + entry.bytes
      }
      Runtime => {
        runtime = runtime + 1
        runtime_bytes = runtime_bytes + entry.bytes
      }
    }
  }
  for plan in plans {
    if plan.aligned {
      aligned = aligned + 1
    } else {
      misaligned = misaligned + 1
    }
  }
  {
    total: manifest.entries.length(),
    immutable,
    revalidate,
    no_store,
    runtime,
    aligned,
    misaligned,
    stability_score: cache_stability_score(manifest),
    immutable_bytes,
    revalidate_bytes,
    no_store_bytes,
    runtime_bytes,
  }
}

///|
pub fn cache_plans(manifest : Manifest) -> Array[CachePlan] {
  let plans : Array[CachePlan] = []
  for entry in manifest.entries {
    let recommended = recommended_policy(entry)
    plans.push({
      path: entry.path,
      current: entry.policy,
      recommended,
      tier: cache_tier(recommended),
      aligned: entry.policy == recommended,
      reason: cache_plan_reason(entry, recommended),
    })
  }
  plans.sort_by((a, b) => a.path.compare(b.path))
  plans
}

///|
pub fn misaligned_cache_plans(manifest : Manifest) -> Array[CachePlan] {
  let result : Array[CachePlan] = []
  for plan in cache_plans(manifest) {
    if !plan.aligned {
      result.push(plan)
    }
  }
  result
}

///|
pub fn aligned_cache_plans(manifest : Manifest) -> Array[CachePlan] {
  let result : Array[CachePlan] = []
  for plan in cache_plans(manifest) {
    if plan.aligned {
      result.push(plan)
    }
  }
  result
}

///|
pub fn assets_requiring_revalidation(manifest : Manifest) -> Array[Asset] {
  let result : Array[Asset] = []
  for entry in manifest.entries {
    if recommended_policy(entry) == Revalidate {
      result.push(entry)
    }
  }
  result
}

///|
pub fn assets_safe_for_immutable(manifest : Manifest) -> Array[Asset] {
  let result : Array[Asset] = []
  for entry in manifest.entries {
    if recommended_policy(entry) == Immutable {
      result.push(entry)
    }
  }
  result
}

///|
pub fn policy_alignment_score(manifest : Manifest) -> Int {
  if manifest.entries.is_empty() {
    return 0
  }
  let aligned = aligned_cache_plans(manifest).length()
  aligned * 100 / manifest.entries.length()
}

///|
pub fn policy_needs_review(manifest : Manifest) -> Bool {
  policy_alignment_score(manifest) < 80 ||
  assets_with_policy(manifest, NoStore).length() > 0 ||
  assets_with_policy(manifest, Runtime).length() > 0
}

///|
pub fn render_policy_profile(profile : PolicyProfile) -> String {
  let out = StringBuilder()
  out.write_string("policy-profile\n")
  out.write_string("total ")
  out.write_string(profile.total.to_string())
  out.write_string("\nimmutable ")
  out.write_string(profile.immutable.to_string())
  out.write_string(" ")
  out.write_string(profile.immutable_bytes.to_string())
  out.write_string("\nrevalidate ")
  out.write_string(profile.revalidate.to_string())
  out.write_string(" ")
  out.write_string(profile.revalidate_bytes.to_string())
  out.write_string("\nno-store ")
  out.write_string(profile.no_store.to_string())
  out.write_string(" ")
  out.write_string(profile.no_store_bytes.to_string())
  out.write_string("\nruntime ")
  out.write_string(profile.runtime.to_string())
  out.write_string(" ")
  out.write_string(profile.runtime_bytes.to_string())
  out.write_string("\naligned ")
  out.write_string(profile.aligned.to_string())
  out.write_string("\nmisaligned ")
  out.write_string(profile.misaligned.to_string())
  out.write_string("\nstability ")
  out.write_string(profile.stability_score.to_string())
  out.write_string("\n")
  out.to_string()
}

///|
pub fn render_cache_plans(plans : Array[CachePlan]) -> String {
  if plans.is_empty() {
    return "(none)\n"
  }
  let out = StringBuilder()
  for plan in plans {
    out.write_string(plan.path)
    out.write_string(" ")
    out.write_string(plan.current.to_wire())
    out.write_string(" -> ")
    out.write_string(plan.recommended.to_wire())
    out.write_string(" ")
    out.write_string(plan.tier.to_wire())
    if plan.aligned {
      out.write_string(" aligned")
    } else {
      out.write_string(" review")
    }
    out.write_string(" - ")
    out.write_string(plan.reason)
    out.write_string("\n")
  }
  out.to_string()
}

///|
fn cache_plan_reason(entry : Asset, recommended : CachePolicy) -> String {
  if entry.policy == recommended {
    match recommended {
      Immutable => "current policy is suitable for fingerprinted stable assets"
      Revalidate => "current policy keeps editable assets refreshable"
      NoStore => "current policy avoids storing transient assets"
      Runtime => "current policy leaves freshness to the caller"
    }
  } else {
    match recommended {
      Immutable => "asset looks stable enough for immutable caching"
      Revalidate => "asset should stay refreshable across deploys"
      NoStore => "asset should avoid persistent caching"
      Runtime => "asset should be managed by the caller at runtime"
    }
  }
}