///| Describes a typed configuration key.
pub struct SchemaField {
  path : String
  kind : String
  required : Bool
  default_value : String?
  description : String
  rules : Array[Rule]
} derive(@debug.Debug, Eq)

///| A named group of related fields.
pub struct SchemaSection {
  name : String
  description : String
  fields : Array[SchemaField]
} derive(@debug.Debug, Eq)

///| A complete configuration contract.
pub struct ConfigSchema {
  name : String
  version : String
  sections : Array[SchemaSection]
} derive(@debug.Debug, Eq)

pub fn field(
  path : String,
  kind : String,
  required? : Bool = false,
  default_value? : String? = None,
  description? : String = "",
  rules? : Array[Rule] = [],
) -> SchemaField {
  { path, kind, required, default_value, description, rules }
}

pub fn section(name : String, description : String, fields : Array[SchemaField]) -> SchemaSection {
  { name, description, fields }
}

pub fn schema(name : String, version : String, sections : Array[SchemaSection]) -> ConfigSchema {
  { name, version, sections }
}

///| Converts schema fields into validation rules.
pub fn ConfigSchema::rules(self : ConfigSchema) -> Array[Rule] {
  let result : Array[Rule] = []
  for schema_section in self.sections {
    for schema_field in schema_section.fields {
      if schema_field.required {
        result.push(required(schema_field.path))
      }
      for rule in schema_field.rules {
        result.push(rule)
      }
    }
  }
  result
}

///| Validates a merged view against a schema.
pub fn ConfigSchema::validate(self : ConfigSchema, view : ConfigView) -> Array[Diagnostic] {
  validate(view, self.rules())
}

///| Returns all known field paths in declaration order.
pub fn ConfigSchema::paths(self : ConfigSchema) -> Array[String] {
  let result : Array[String] = []
  for schema_section in self.sections {
    for schema_field in schema_section.fields {
      result.push(schema_field.path)
    }
  }
  result
}

///| Finds schema metadata for one path.
pub fn ConfigSchema::find_field(self : ConfigSchema, path : String) -> SchemaField? {
  for schema_section in self.sections {
    for schema_field in schema_section.fields {
      if schema_field.path == path {
        return Some(schema_field)
      }
    }
  }
  None
}

///| Reports values present in the config but unknown to the schema.
pub fn ConfigSchema::unknown_key_diagnostics(self : ConfigSchema, view : ConfigView) -> Array[Diagnostic] {
  let diagnostics : Array[Diagnostic] = []
  let known = self.paths()
  for item in view.values {
    let path = entry_path(item.section, item.key)
    if !array_contains(known, path) {
      diagnostics.push(warning("unknown-key", "key '" + path + "' is not declared in schema '" + self.name + "'", span=Some(item.span)))
    }
  }
  diagnostics
}

///| Produces a Markdown contract table.
pub fn ConfigSchema::render_markdown(self : ConfigSchema) -> String {
  let builder = StringBuilder()
  builder.write_string("# " + self.name + " Configuration Schema\n\n")
  builder.write_string("Version: " + self.version + "\n\n")
  for schema_section in self.sections {
    builder.write_string("## " + schema_section.name + "\n\n")
    if schema_section.description != "" {
      builder.write_string(schema_section.description + "\n\n")
    }
    builder.write_string("| Path | Type | Required | Default | Description |\n")
    builder.write_string("| --- | --- | --- | --- | --- |\n")
    for schema_field in schema_section.fields {
      builder.write_string("| " + schema_field.path + " | " + schema_field.kind + " | " + bool_label(schema_field.required) + " | " + option_or_empty(schema_field.default_value) + " | " + schema_field.description + " |\n")
    }
    builder.write_string("\n")
  }
  builder.to_string()
}

fn bool_label(value : Bool) -> String {
  if value { "yes" } else { "no" }
}

fn option_or_empty(value : String?) -> String {
  match value {
    Some(text) => text
    None => ""
  }
}

fn array_contains(values : Array[String], target : String) -> Bool {
  for value in values {
    if value == target {
      return true
    }
  }
  false
}

///| A reusable schema for small web services.
pub fn web_service_schema() -> ConfigSchema {
  schema("web-service", "1.0", [
    section("server", "HTTP listener and runtime behavior.", [
      field("server.host", "string", required=true, description="Bind address or host name."),
      field("server.port", "int", required=true, description="TCP port.", rules=[int_range("server.port", Some(1), Some(65535))]),
      field("server.mode", "enum", required=false, default_value=Some("development"), description="Runtime mode.", rules=[enum_value("server.mode", ["development", "test", "production"])]),
      field("server.workers", "int", required=false, default_value=Some("1"), description="Worker count.", rules=[int_range("server.workers", Some(1), Some(256))]),
      field("server.request_timeout_ms", "int", required=false, default_value=Some("30000"), description="Request timeout in milliseconds.", rules=[int_range("server.request_timeout_ms", Some(1), Some(600000))]),
    ]),
    section("database", "Primary database connection settings.", [
      field("database.url", "string", required=true, description="Database connection URL."),
      field("database.pool_min", "int", required=false, default_value=Some("1"), description="Minimum pool size.", rules=[int_range("database.pool_min", Some(0), Some(1024))]),
      field("database.pool_max", "int", required=false, default_value=Some("16"), description="Maximum pool size.", rules=[int_range("database.pool_max", Some(1), Some(4096))]),
      field("database.password", "secret", required=false, description="Database password.", rules=[secret_like("database.password")]),
    ]),
    section("auth", "Authentication and authorization settings.", [
      field("auth.enabled", "bool", required=false, default_value=Some("false"), description="Enable authentication."),
      field("auth.token", "secret", required=false, description="Static service token.", rules=[secret_like("auth.token")]),
      field("auth.oauth", "bool", required=false, description="Enable OAuth."),
      field("auth.client_id", "string", required=false, description="OAuth client id.", rules=[requires("auth.oauth", "auth.client_id")]),
      field("auth.basic", "bool", required=false, description="Enable basic auth.", rules=[conflicts("auth.oauth", "auth.basic")]),
    ]),
  ])
}

///| A reusable schema for CLI tools.
pub fn cli_tool_schema() -> ConfigSchema {
  schema("cli-tool", "1.0", [
    section("output", "Command output behavior.", [
      field("output.format", "enum", required=false, default_value=Some("text"), description="Output format.", rules=[enum_value("output.format", ["text", "json", "markdown"])]),
      field("output.color", "enum", required=false, default_value=Some("auto"), description="Color mode.", rules=[enum_value("output.color", ["auto", "always", "never"])]),
      field("output.quiet", "bool", required=false, default_value=Some("false"), description="Suppress non-error output."),
    ]),
    section("runtime", "Runtime limits and cache settings.", [
      field("runtime.threads", "int", required=false, default_value=Some("1"), description="Worker thread count.", rules=[int_range("runtime.threads", Some(1), Some(128))]),
      field("runtime.cache_dir", "path", required=false, description="Cache directory path."),
      field("runtime.timeout_ms", "int", required=false, default_value=Some("30000"), description="Global timeout.", rules=[int_range("runtime.timeout_ms", Some(1), Some(86400000))]),
    ]),
  ])
}

///| A reusable schema for test environments.
pub fn test_environment_schema() -> ConfigSchema {
  schema("test-environment", "1.0", [
    section("test", "Test selection and failure policy.", [
      field("test.pattern", "string", required=false, default_value=Some("*"), description="Test name filter."),
      field("test.retries", "int", required=false, default_value=Some("0"), description="Retry count.", rules=[int_range("test.retries", Some(0), Some(20))]),
      field("test.fail_fast", "bool", required=false, default_value=Some("false"), description="Stop after the first failure."),
    ]),
    section("fixture", "Fixture and sandbox options.", [
      field("fixture.root", "path", required=true, description="Fixture root directory."),
      field("fixture.reset", "bool", required=false, default_value=Some("true"), description="Reset fixtures before running."),
      field("fixture.seed", "int", required=false, default_value=Some("1"), description="Deterministic seed.", rules=[int_range("fixture.seed", Some(0), None)]),
    ]),
  ])
}