# Variables ====================================================================

variable "bucket_name" {
  description = "Globally unique S3 bucket name"
  type        = string

  validation {
    condition     = can(regex("^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$", var.bucket_name))
    error_message = <<-EOT
      bucket_name must be 3-63 characters, lowercase letters, numbers,
      dots, and hyphens only, and must start and end with a letter
      or number.
    EOT
  }

  validation {
    condition     = !can(regex("\\.\\.", var.bucket_name))
    error_message = "bucket_name must not contain consecutive periods."
  }

  validation {
    condition     = !can(regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", var.bucket_name))
    error_message = "bucket_name must not be formatted as an IP address."
  }
}

variable "force_destroy" {
  description = "Allow destruction of non-empty bucket"
  type        = bool
  default     = false
}

variable "versioning_enabled" {
  description = "Enable object versioning"
  type        = bool
  default     = true
}

variable "kms_key_id" {
  description = "KMS key ARN for server-side encryption (null uses aws:kms with managed key)"
  type        = string
  default     = null

  validation {
    condition     = var.kms_key_id == null || can(regex("^arn:aws[a-zA-Z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$", var.kms_key_id))
    error_message = "kms_key_id must be a valid KMS key ARN (arn:aws:kms:<region>:<account>:key/<id>)."
  }
}

variable "sse_algorithm" {
  description = "SSE algorithm (aws:kms or AES256)"
  type        = string
  default     = "aws:kms"

  validation {
    condition     = contains(["aws:kms", "AES256"], var.sse_algorithm)
    error_message = "sse_algorithm must be 'aws:kms' or 'AES256'."
  }

  validation {
    condition     = var.sse_algorithm == "aws:kms" || var.kms_key_id == null
    error_message = <<-EOT
      kms_key_id must not be set when sse_algorithm is "AES256".
      AES256 does not use a KMS key, and the value would be
      silently ignored.
    EOT
  }
}

variable "bucket_key_enabled" {
  description = "Enable S3 bucket key to reduce KMS API calls"
  type        = bool
  default     = true
}

variable "block_public_acls" {
  description = "Block public ACLs"
  type        = bool
  default     = true
}

variable "block_public_policy" {
  description = "Block public bucket policies"
  type        = bool
  default     = true
}

variable "ignore_public_acls" {
  description = "Ignore public ACLs"
  type        = bool
  default     = true
}

variable "restrict_public_buckets" {
  description = "Restrict public bucket access"
  type        = bool
  default     = true
}

variable "lifecycle_rules" {
  description = "Map of lifecycle rules to apply"
  type = map(object({
    enabled                                = optional(bool, true)
    prefix                                 = optional(string, null)
    abort_incomplete_multipart_upload_days = optional(number, 7)
    expiration_days                        = optional(number, null)
    noncurrent_version_expiration_days     = optional(number, 90)
    transitions = optional(list(object({
      days          = number
      storage_class = string
    })), [])
  }))
  default = {}

  validation {
    condition = alltrue(flatten([
      for rule_key, rule in var.lifecycle_rules : [
        for t in rule.transitions :
        contains(local.allowed_storage_classes, t.storage_class)
      ]
    ]))
    error_message = "One or more lifecycle transition storage_class values are not valid. Must be one of the following: ${join(", ", local.allowed_storage_classes)}."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.lifecycle_rules :
      rule.abort_incomplete_multipart_upload_days > 0
    ])
    error_message = "abort_incomplete_multipart_upload_days must be greater than 0 for every lifecycle rule."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.lifecycle_rules :
      rule.expiration_days == null || alltrue([
        for t in rule.transitions : t.days < rule.expiration_days
      ])
    ])
    error_message = "Every transition's days must be less than expiration_days for rules where expiration_days is set."
  }
}

variable "logging_target_bucket" {
  description = "S3 bucket name to receive access logs (optional)"
  type        = string
  default     = null
}

variable "logging_target_prefix" {
  description = "Prefix for access log objects"
  type        = string
  default     = "logs/"
}

variable "replication_role_arn" {
  description = "IAM role ARN for S3 replication (required when replication_rules is set)"
  type        = string
  default     = null

  validation {
    condition     = var.replication_role_arn == null || can(regex("^arn:aws[a-zA-Z-]*:iam::[0-9]{12}:role/.+$", var.replication_role_arn))
    error_message = "replication_role_arn must be a valid IAM role ARN."
  }

  validation {
    condition     = length(var.replication_rules) == 0 || var.replication_role_arn != null
    error_message = "replication_role_arn is required when replication_rules is set."
  }
}

variable "replication_rules" {
  description = "Map of replication rules"
  type = map(object({
    destination_bucket_arn    = string
    destination_kms_key_id    = optional(string, null)
    destination_account_id    = optional(string, null)
    prefix                    = optional(string, "")
    priority                  = optional(number, 1)
    delete_marker_replication = optional(bool, true)
  }))
  default = {}

  validation {
    condition     = length(var.replication_rules) == 0 || var.versioning_enabled
    error_message = "versioning_enabled must be true when replication_rules is set."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.replication_rules :
      can(regex("^arn:aws[a-zA-Z-]*:s3:::[a-z0-9.-]+$", rule.destination_bucket_arn))
    ])
    error_message = "Every replication rule's destination_bucket_arn must be a valid S3 bucket ARN."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.replication_rules :
      rule.destination_account_id == null || can(regex("^[0-9]{12}$", rule.destination_account_id))
    ])
    error_message = "destination_account_id, when set, must be a 12-digit AWS account ID."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.replication_rules :
      rule.priority >= 0
    ])
    error_message = "priority must be 0 or greater for every replication rule."
  }

  validation {
    condition = alltrue([
      for rule_key, rule in var.replication_rules :
      rule.destination_kms_key_id == null || can(regex("^arn:aws[a-zA-Z-]*:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$", rule.destination_kms_key_id))
    ])
    error_message = "destination_kms_key_id, when set, must be a valid KMS key ARN for every replication rule."
  }

  validation {
    condition     = length(distinct([for rule_key, rule in var.replication_rules : rule.priority])) == length(var.replication_rules)
    error_message = "Every replication rule must have a unique priority."
  }
}

variable "policy" {
  description = "JSON bucket policy document (optional)"
  type        = string
  default     = null

  validation {
    condition     = var.policy == null || can(jsondecode(var.policy))
    error_message = "policy must be valid JSON."
  }
}

variable "object_ownership" {
  description = "Object ownership"
  type        = string
  default     = "BucketOwnerEnforced"

  validation {
    condition     = contains(local.allowed_object_ownership_values, var.object_ownership)
    error_message = "object_ownership must be one of the following: ${join(", ", local.allowed_object_ownership_values)}."
  }
}

variable "object_lock_enabled" {
  description = <<-EOT
    Enable S3 Object Lock on the bucket. Can only be set at bucket
    creation, cannot be enabled on an existing bucket. Requires
    versioning_enabled = true.
  EOT
  type        = bool
  default     = false

  validation {
    condition     = !var.object_lock_enabled || var.versioning_enabled
    error_message = <<-EOT
      object_lock_enabled requires versioning_enabled to also be
      true. Object Lock cannot function without versioning.
    EOT
  }
}

variable "object_lock_default_retention" {
  description = <<-EOT
    Default retention rule applied to objects when Object Lock is
    enabled. mode must be 'GOVERNANCE' or 'COMPLIANCE'. Only used
    when object_lock_enabled is true.
  EOT
  type = object({
    mode = string
    days = number
  })
  default = null

  validation {
    condition = (
      var.object_lock_default_retention == null
      || contains(["GOVERNANCE", "COMPLIANCE"], var.object_lock_default_retention.mode)
    )
    error_message = <<-EOT
      object_lock_default_retention.mode must be either
      'GOVERNANCE' or 'COMPLIANCE'.
    EOT
  }

  validation {
    condition = (
      var.object_lock_default_retention == null
      || var.object_lock_default_retention.days > 0
    )
    error_message = <<-EOT
      object_lock_default_retention.days must be greater than 0.
    EOT
  }
}

variable "tags" {
  description = "Resource tags to apply to all resources"
  type        = map(string)
  default     = {}
}
