///| 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"),
  ]
}