///|
/// A suggested configuration key migration.
pub struct RenameRule {
  old_path : String
  new_path : String
  reason : String
} derive(@debug.Debug, Eq)

///|
/// A concrete migration action derived from a view.
pub struct MigrationAction {
  old_path : String
  new_path : String
  value : String
  source_layer : String
  reason : String
} derive(@debug.Debug, Eq)

///|
pub fn rename_rule(
  old_path : String,
  new_path : String,
  reason? : String = "",
) -> RenameRule {
  { old_path, new_path, reason }
}

///|
/// Builds migration actions for rename rules that match existing keys.
pub fn plan_renames(
  view : ConfigView,
  rules : Array[RenameRule],
) -> Array[MigrationAction] {
  let actions : Array[MigrationAction] = []
  for rule in rules {
    match find_resolved_by_path(view, rule.old_path) {
      None => ()
      Some(item) =>
        actions.push({
          old_path: rule.old_path,
          new_path: rule.new_path,
          value: item.value,
          source_layer: item.source_layer,
          reason: rule.reason,
        })
    }
  }
  actions
}

///|
fn find_resolved_by_path(view : ConfigView, path : String) -> ResolvedValue? {
  for item in view.values {
    if entry_path(item.section, item.key) == path {
      return Some(item)
    }
  }
  None
}

///|
/// Renders migration actions as a readable checklist.
pub fn render_migration_plan(actions : Array[MigrationAction]) -> String {
  let builder = StringBuilder()
  builder.write_string("# Configuration Migration Plan\n\n")
  if actions.length() == 0 {
    builder.write_string("No matching migration actions.\n")
  } else {
    for action in actions {
      builder.write_string(
        "- Rename `" + action.old_path + "` to `" + action.new_path + "`",
      )
      builder.write_string(
        " using value `" +
        redact_value(action.old_path, action.value) +
        "` from layer `" +
        action.source_layer +
        "`",
      )
      if action.reason != "" {
        builder.write_string(". Reason: " + action.reason)
      }
      builder.write_string("\n")
    }
  }
  builder.to_string()
}

///|
/// Applies rename actions into a new properties-style patch.
pub fn render_migration_patch(actions : Array[MigrationAction]) -> String {
  let builder = StringBuilder()
  for action in actions {
    builder.write_string("# migrated from " + action.old_path + "\n")
    builder.write_string(
      action.new_path + "=" + action.value.replace(old="\n", new="\\n") + "\n",
    )
  }
  builder.to_string()
}

///|
/// Compares required schema keys across multiple profiles.
pub fn profile_gap_report(
  schema : ConfigSchema,
  profiles : Array[(String, ConfigView)],
) -> Array[Diagnostic] {
  let diagnostics : Array[Diagnostic] = []
  for pair in profiles {
    let profile_name = pair.0
    let view = pair.1
    for schema_section in schema.sections {
      for schema_field in schema_section.fields {
        if schema_field.required && view.get_path(schema_field.path) == None {
          diagnostics.push(
            error(
              "profile-missing",
              "profile '" +
              profile_name +
              "' misses required key '" +
              schema_field.path +
              "'",
            ),
          )
        }
      }
    }
  }
  diagnostics
}

///|
/// Finds keys that are only present in some profiles.
pub fn profile_presence_matrix(
  profiles : Array[(String, ConfigView)],
) -> String {
  let all_paths = collect_all_profile_paths(profiles)
  let builder = StringBuilder()
  builder.write_string("| Path |")
  for pair in profiles {
    builder.write_string(" " + pair.0 + " |")
  }
  builder.write_string("\n| --- |")
  for _ in profiles {
    builder.write_string(" --- |")
  }
  builder.write_string("\n")
  for path in all_paths {
    builder.write_string("| " + path + " |")
    for pair in profiles {
      if pair.1.get_path(path) == None {
        builder.write_string(" no |")
      } else {
        builder.write_string(" yes |")
      }
    }
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
fn collect_all_profile_paths(
  profiles : Array[(String, ConfigView)],
) -> Array[String] {
  let result : Array[String] = []
  for pair in profiles {
    for item in pair.1.values {
      let path = entry_path(item.section, item.key)
      if !contains_profile_path(result, path) {
        result.push(path)
      }
    }
  }
  result
}

///|
fn contains_profile_path(paths : Array[String], target : String) -> Bool {
  for path in paths {
    if path == target {
      return true
    }
  }
  false
}

///|
/// Common migration rules for older service configs.
pub fn common_service_rename_rules() -> Array[RenameRule] {
  [
    rename_rule(
      "server.bind",
      "server.host",
      reason="server.host is clearer for host names and IP addresses",
    ),
    rename_rule(
      "server.listen",
      "server.port",
      reason="server.port separates address and port concerns",
    ),
    rename_rule(
      "db.url",
      "database.url",
      reason="database section uses full names",
    ),
    rename_rule(
      "db.pass",
      "database.password",
      reason="secret keys should use explicit names",
    ),
    rename_rule(
      "auth.legacy_token",
      "auth.token",
      reason="legacy_token is kept for backward compatibility only",
    ),
    rename_rule(
      "auth.client",
      "auth.client_id",
      reason="OAuth settings use provider terminology",
    ),
  ]
}