///|
/// show how to combine multiple schemas into one schema.
fn full_user_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "id": uuid_schema(),
    "ip": ip_address_schema(),
    "name": @moon_zod.string()
    .min(2)
    .max(50)
    .describe("The user's display name, 2-50 characters"),
    "age": @moon_zod.number().int().min(0).max(150),
    "email": email_schema(),
    "bio": @moon_zod.string().max(500).optional().describe("Short biography"),
    "score": @moon_zod.number().int().min(0).max(100).optional(),
    "profile": optional_profile_schema(),
    "tags": @moon_zod.array(@moon_zod.string().min(1))
    .min(1)
    .max(10)
    .describe("Up to 10 tags"),
    "metadata": @moon_zod.object({
      "source": @moon_zod.string().describe("Where the user came from"),
      "is_verified": @moon_zod.boolean().default(false),
      "login_count": @moon_zod.number().int().min(0).default(0),
    })
    .optional()
    .describe("Optional metadata object"),
    "notifications_enabled": @moon_zod.boolean().optional().default(true),
  }).name("FullUser")
}

///|
/// show how to use the email schema in another schema
fn email_schema() -> @moon_zod.Schema {
  @moon_zod.string().email().name("Email")
}

///|
/// show how to use the url schema in another schema
fn url_schema() -> @moon_zod.Schema {
  @moon_zod.string().url().name("Url")
}

///|
/// show how to use the uuid schema in another schema
fn uuid_schema() -> @moon_zod.Schema {
  @moon_zod.string().uuid().name("Uuid")
}

///|
/// show how to use the ip schema in another schema
fn ip_address_schema() -> @moon_zod.Schema {
  @moon_zod.string().ip().name("IpAddress")
}

///|
/// show how to use partial to make all properties optional
fn optional_profile_schema() -> @moon_zod.Schema {
  fn profile_schema() -> @moon_zod.Schema {
    @moon_zod.object({
      "bio": @moon_zod.string().max(500),
      "avatar_url": url_schema(),
    }).name("Profile")
  }
  let profile = profile_schema()
  profile.partial().name("OptionalProfile")
}

///|
/// show how to use omit and pick to create a new schema based on an existing one
fn user_schema() -> @moon_zod.Schema {
  let full_user = full_user_schema()
  let user_without_email = full_user.omit(["email"])
  let user_with_only_name_and_age = user_without_email.pick(["name", "age"])
  user_with_only_name_and_age.name("User")
}

///|
/// show how to use extend to create a new schema based on an existing one
#warnings("-unused_value")
fn extended_user_schema() -> @moon_zod.Schema {
  let user = user_schema()
  let extended_user = user.extend_with({
    "email": email_schema(),
    "profile": optional_profile_schema(),
  })
  extended_user.name("ExtendedUser")
}

///|
/// show how to use merge to create a new schema based on two existing ones
#warnings("-unused_value")
fn merged_user_schema() -> @moon_zod.Schema {
  let user = user_schema()
  let extended_user = user.extend_with({
    "email": email_schema(),
    "profile": optional_profile_schema(),
  })
  let merged_user = user.merge(extended_user)
  merged_user.name("MergedUser")
}

///|
/// show how to use tuple to create a schema for a fixed-length array
fn tuple_schema() -> @moon_zod.Schema {
  @moon_zod.tuple([
    @moon_zod.string().min(1).describe("First element"),
    @moon_zod.number().int().min(0).describe("Second element"),
    @moon_zod.boolean().describe("Third element"),
  ]).name("Tuple")
}

///|
/// show how to use any/unknown to create a schema that accepts any value
#warnings("-unused_value")
fn any_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "data": @moon_zod.any().describe("Any value"),
    "other": @moon_zod.unknown().describe("Unknown value"),
  }).name("AnySchema")
}

///|
/// show how to use preprocess to create a schema that transforms input before validation, use transform to create a schema that transforms output after validation
fn preprocess_transform_schema() -> @moon_zod.Schema {
  let transform_schema = @moon_zod.string().transform(fn(json) {
    match json {
      String(v) => Ok(Json::string(v.to_upper()))
      _ => Err("expected string")
    }
  })
  let preprocess_schema = @moon_zod.preprocess(
    fn(json) {
      match json {
        Number(v, ..) => Ok(Json::string(v.to_string()))
        String(v) => Ok(Json::string(v.to_lower()))
        _ => Ok(json)
      }
    },
    transform_schema,
  )
  preprocess_schema.name("PreprocessTransformSchema")
}

///|
/// show how to use refine to add custom validation logic
fn product_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "name": @moon_zod.string()
    .min(1)
    .describe("Product display name")
    .refine(val => !(val == "invalid"), "product name cannot be 'invalid'"),
    "price": @moon_zod.number().positive().describe("Price in dollars"),
    "currency": @moon_zod.string()
    .length(3)
    .default("USD")
    .describe("ISO 4217 currency code"),
    "in_stock": @moon_zod.boolean()
    .default(true)
    .describe("Whether the product is currently in stock"),
    "metadata": @moon_zod.object({
      "description": @moon_zod.string().max(2000).optional(),
      "images": @moon_zod.array(@moon_zod.string().url())
      .max(10)
      .describe("Product image URLs"),
    })
    .optional()
    .name("ProductMetadata"),
  }).name("Product")
}

///|
/// show how to use enum values with exclude and extract
fn order_status_schema() -> @moon_zod.Schema {
  @moon_zod.enum_values(["pending", "shipped", "delivered", "cancelled"])
  .name("OrderStatus")
  .describe("The status of an order")
}

///|
/// show how to use exclude to remove specific enum values
#warnings("-unused_value")
fn active_status_schema() -> @moon_zod.Schema {
  order_status_schema()
  .exclude(["cancelled"])
  .name("ActiveStatus")
  .describe("Order statuses that are still active")
}

///|
/// show how to use extract to keep only specific enum values
#warnings("-unused_value")
fn completed_status_schema() -> @moon_zod.Schema {
  order_status_schema()
  .extract(["delivered", "cancelled"])
  .name("CompletedStatus")
  .describe("Order statuses that are no longer active")
}

///|
/// show how to use pipe for explicit two-stage validation.
/// The input is first validated against the first schema, transformed, then
/// validated against the second schema. Errors from each stage are reported
/// independently.
#warnings("-unused_value")
fn pipe_schema() -> @moon_zod.Schema {
  @moon_zod.string()
  .transform(fn(json) {
    match json {
      String(v) => Ok(Json::number(v.length().to_double()))
      _ => Err("expected string")
    }
  })
  .pipe(@moon_zod.number().int().min(3).max(10))
  .name("StringLength")
  .describe("Validates string length is between 3 and 10")
}

///|
/// show how to use union types
fn address_schema() -> @moon_zod.Schema {
  fn home_address_schema() -> @moon_zod.Schema {
    @moon_zod.object({
      "type": @moon_zod.literal("home"),
      "street": @moon_zod.string().min(1),
      "city": @moon_zod.string().min(1),
    }).name("HomeAddress")
  }
  fn office_address_schema() -> @moon_zod.Schema {
    @moon_zod.object({
      "type": @moon_zod.literal("office"),
      "company": @moon_zod.string().min(1),
    }).name("OfficeAddress")
  }
  @moon_zod.union([home_address_schema(), office_address_schema()]).name(
    "Address",
  )
}

///|
/// show how to use intersection types
fn order_info_schema() -> @moon_zod.Schema {
  @moon_zod.intersection([order_id_schema(), datetime_schema()]).name(
    "OrderInfo",
  )
}

///|
/// show how to use datetime schema
fn datetime_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "created_at": @moon_zod.string()
    .datetime()
    .describe("The time the order was created"),
    "updated_at": @moon_zod.string()
    .datetime()
    .describe("The last time the order was updated"),
  }).name("DateTime")
}

///|
/// show how to use intersection types (merge two schemas into one, the same fields will merge all rules together)
fn order_id_schema() -> @moon_zod.Schema {
  fn order_id_positive_schema() -> @moon_zod.Schema {
    @moon_zod.object({ "id": @moon_zod.number().int().positive() })
  }
  fn order_id_smaller_than_100_schema() -> @moon_zod.Schema {
    @moon_zod.object({ "id": @moon_zod.number().int().max(100) })
  }
  @moon_zod.intersection([
    order_id_positive_schema(),
    order_id_smaller_than_100_schema(),
  ]).name("OrderId")
}

///|
/// show how to use transform
fn review_score_schema() -> @moon_zod.Schema {
  @moon_zod.number()
  .int()
  .positive()
  .max(5)
  .describe("Rating from 1 to 5")
  .transform(fn(j) { Ok(j) })
  .name("ReviewScore")
}

///|
/// show how to use null type and default with describe
fn shipping_info_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "address": address_schema(),
    "tracking_number": @moon_zod.string()
    .optional()
    .describe("Tracking number, available after shipment"),
    "estimated_days": @moon_zod.number()
    .int()
    .min(1)
    .max(30)
    .default(5)
    .describe("Estimated delivery days"),
    "gift_wrap": @moon_zod.boolean()
    .default(false)
    .describe("Whether to gift wrap the order"),
    "special_instructions": @moon_zod.string()
    .optional()
    .describe("Any special delivery instructions"),
  }).name("ShippingInfo")
}

///|
/// output schema for an order, combining multiple schemas into one
/// will be used to show the export of `ts interface prompt` and `json schema` and `moonbit struct`.
fn order_schema() -> @moon_zod.Schema {
  @moon_zod.object({
    "user": user_schema(),
    "product": product_schema(),
    "status": order_status_schema(),
    "quantity": @moon_zod.number()
    .int()
    .positive()
    .default(1)
    .describe("Number of items ordered"),
    "address": address_schema(),
    "info": order_info_schema(),
    "shipping": shipping_info_schema(),
    "review_score": review_score_schema().optional(),
    "notes": @moon_zod.string().optional().describe("Optional order notes"),
    "is_priority": @moon_zod.boolean()
    .default(false)
    .describe("Whether this is a priority order"),
    "coupon_code": @moon_zod.string()
    .optional()
    .describe("Optional coupon code"),
    "preprocessed_field": preprocess_transform_schema().optional(),
    "tuple_field": tuple_schema().optional(),
  }).name("Order")
}