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