# Nickel Schema with Type-Safe Contracts and Validators
#
# This demonstrates how Nickel contracts provide compile-time validation
# and type-safety for form schemas.
#
# Key concepts:
# - `|` operator applies contracts (validators)
# - `std.string.NonEmpty` ensures non-empty strings
# - `std.number.Between` validates number ranges
# - `std.types.Dynamic` allows flexible types
# - Custom contracts for business logic
#
# Usage:
#   1. Evaluate: nickel eval nickel-validators.ncl
#   2. Query inputs: nickel query nickel-validators.ncl inputs
#   3. Convert to TOML: nickel query nickel-validators.ncl inputs | typedialog parse-nickel
#

# ====================================================================
# STANDARD LIBRARY IMPORTS FOR VALIDATION
# ====================================================================

let std = import "std" in

# ====================================================================
# CUSTOM CONTRACTS (Business Logic Validators)
# ====================================================================

# Email validation contract
let EmailContract = std.contract.from_predicate (
  fun x =>
    let email_pattern = "^[^@]+@[^@]+\\.[^@]+$" in
    if std.string.is_match email_pattern x then true
    else std.contract.blame_with ("Email must be in format: user@domain.com")
)
in

# Username validation: 3-32 chars, alphanumeric + underscore
let UsernameContract = std.contract.from_predicate (
  fun x =>
    let length_ok = (std.string.length x >= 3 && std.string.length x <= 32) in
    if length_ok then true
    else std.contract.blame_with ("Username must be 3-32 characters")
)
in

# Password validation: at least 8 chars, uppercase, lowercase, digit
let PasswordContract = std.contract.from_predicate (
  fun x =>
    let has_length = std.string.length x >= 8 in
    let has_upper = std.string.is_match "[A-Z]" x in
    let has_lower = std.string.is_match "[a-z]" x in
    let has_digit = std.string.is_match "[0-9]" x in
    if has_length && has_upper && has_lower && has_digit then true
    else std.contract.blame_with ("Password must have: 8+ chars, uppercase, lowercase, digits")
)
in

# ====================================================================
# MAIN SCHEMA
# ====================================================================

{
  # Personal Information Section
  personal = {
    # Username with custom contract
    username | UsernameContract = "default_user",

    # Email with custom contract
    email | EmailContract = "",

    # First name - non-empty string
    first_name | std.string.NonEmpty = "",

    # Last name - non-empty string
    last_name | std.string.NonEmpty = "",

    # Age - number between 18 and 100
    age | std.number.Between 18 100 = 25,

    # Bio - optional, max 500 chars
    bio | (std.string.MaxLength 500 | std.contract.optional) = null,
  },

  # Authentication Section
  authentication = {
    # Password with strong validation
    password | PasswordContract = "",

    # Confirm password (should match)
    password_confirm | PasswordContract = "",

    # Enable 2FA
    enable_2fa | Bool = false,

    # Backup codes (only when 2FA enabled)
    backup_codes | (std.contract.list (std.string.NonEmpty) | std.contract.optional) = null,
  },

  # Account Type and Role-Based Fields
  account = {
    # Account type - enum-like values
    account_type | std.contract.EnumContract ["personal", "business", "nonprofit", "education"] = "personal",

    # Role - based on account type
    role | std.contract.EnumContract ["user", "moderator", "admin"] = "user",

    # Permissions - list of strings
    permissions | std.contract.list std.string.NonEmpty = [],
  },

  # Business Information (conditional)
  business | std.contract.optional = {
    # Company name - required if business account
    company_name | std.string.NonEmpty = "",

    # Tax ID
    tax_id | (std.string.NonEmpty | std.contract.optional) = null,

    # Employee count
    employee_count | std.number.Between 1 100000 = 1,

    # Industry
    industry | std.string.NonEmpty = "",
  },

  # Preferences and Settings
  preferences = {
    # Language preference
    language | std.contract.EnumContract ["en", "es", "fr", "de", "ja"] = "en",

    # Timezone
    timezone | std.string.NonEmpty = "UTC",

    # Notification settings
    notifications = {
      # Email notifications enabled
      email | Bool = true,

      # Notification frequency
      frequency | std.contract.EnumContract ["instant", "daily", "weekly", "never"] = "daily",

      # Categories to subscribe to
      categories | std.contract.list (
        std.contract.EnumContract ["updates", "security", "promotions", "newsletter"]
      ) = ["updates", "security"],
    },

    # UI theme preference
    theme | std.contract.EnumContract ["light", "dark", "auto"] = "auto",
  },

  # Server Configuration (with numeric constraints)
  server | std.contract.optional = {
    # Hostname validation
    hostname | std.string.NonEmpty = "",

    # Port with range validation
    port | std.number.Between 1 65535 = 8080,

    # Max connections - positive number
    max_connections | std.number.Between 1 100000 = 1000,

    # Connection timeout in seconds
    timeout_seconds | std.number.Between 1 3600 = 30,

    # Enable SSL
    enable_ssl | Bool = true,

    # SSL cert path (required if SSL enabled)
    ssl_cert_path | (std.string.NonEmpty | std.contract.optional) = null,
  },

  # API Configuration
  api | std.contract.optional = {
    # API key - non-empty, alphanumeric
    api_key | std.string.NonEmpty = "",

    # Rate limit - requests per minute
    rate_limit_rpm | std.number.Between 1 10000 = 1000,

    # Allowed origins (CORS)
    cors_origins | std.contract.list std.string.NonEmpty = ["*"],

    # API version
    api_version | std.contract.EnumContract ["v1", "v2", "v3"] = "v3",
  },

  # Validation Metadata
  metadata = {
    # Account created timestamp
    created_at | std.string.NonEmpty = "",

    # Last updated timestamp
    updated_at | std.string.NonEmpty = "",

    # Account status
    status | std.contract.EnumContract ["active", "suspended", "closed"] = "active",

    # Validation version (for schema evolution)
    schema_version | String = "1.0",
  },
}
