///|
pub impl @json.FromJson for Model with fn from_json(
  json : Json,
  json_path : @json.JsonPath,
) -> Model {
  guard json is Object(json) else {
    raise JsonDecodeError(
      (json_path, "@vectie/moonclaw/model.Model::from_json: expected object"),
    )
  }
  let name : String = {
    guard json.get("name") is Some(name) else {
      raise JsonDecodeError(
        (
          json_path, "@vectie/moonclaw/model.Model::from_json: missing 'name' field",
        ),
      )
    }
    @json.from_json(name, path=json_path.add_key("name"))
  }
  let model_name : String = {
    guard json.get("model_name") is Some(model_name) else {
      raise JsonDecodeError(
        (
          json_path, "@vectie/moonclaw/model.Model::from_json: missing 'model_name' field",
        ),
      )
    }
    @json.from_json(model_name, path=json_path.add_key("model_name"))
  }
  let model_type : Type = {
    guard json.get("model_type") is Some(model_type) else {
      raise JsonDecodeError(
        (
          json_path, "@vectie/moonclaw/model.Model::from_json: missing 'model_type' field",
        ),
      )
    }
    @json.from_json(model_type, path=json_path.add_key("model_type"))
  }
  let api_key : String = {
    guard json.get("api_key") is Some(api_key) else { "" }
    @json.from_json(api_key, path=json_path.add_key("api_key"))
  }
  let base_url : String = {
    guard json.get("base_url") is Some(base_url) else {
      match model_type {
        SaaS(OpenAI) => "https://api.openai.com/v1"
        SaaS(Anthropic) => "https://api.anthropic.com"
        SaaS(CodexOAuth) => "https://chatgpt.com/backend-api"
        SaaS(Copilot) => "https://api.githubcopilot.com"
        SaaS(Qwen) => "https://dashscope.aliyuncs.com/compatible-mode/v1"
        SaaS(Kimi) => "https://api.moonshot.cn/v1"
        SaaS(KimiCoding) => "https://api.moonshot.cn/v1"
      }
    }
    @json.from_json(base_url, path=json_path.add_key("base_url"))
  }
  let description : String? = {
    guard json.get("description") is Some(description) else { None }
    Some(@json.from_json(description, path=json_path.add_key("description")))
  }
  let safe_zone_tokens : Int = {
    guard json.get("safe_zone_tokens") is Some(safe_zone_tokens) else {
      // raise @json.JsonDecodeError(
      //   (
      //     json_path, "@vectie/moonclaw/model.Model::from_json: missing 'safe_zone_tokens' field",
      //   ),
      // )
      128_000
    }
    @json.from_json(
      safe_zone_tokens,
      path=json_path.add_key("safe_zone_tokens"),
    )
  }
  let supports_anthropic_prompt_caching : Bool = {
    guard json.get("supports_anthropic_prompt_caching")
      is Some(supports_anthropic_prompt_caching) else {
      false
    }
    @json.from_json(
      supports_anthropic_prompt_caching,
      path=json_path.add_key("supports_anthropic_prompt_caching"),
    )
  }
  let supports_apply_patch : Bool = {
    guard json.get("supports_apply_patch") is Some(supports_apply_patch) else {
      // Auto-detect based on model name for GPT 5.1+
      let name_lower = model_name.to_lower()
      name_lower.contains("gpt-5.1") ||
      name_lower.contains("gpt-5.2") ||
      name_lower.contains("gpt-5.3") ||
      name_lower.contains("gpt-5.4") ||
      name_lower.contains("gpt-5.5") ||
      name_lower.contains("gpt-5.6")
    }
    @json.from_json(
      supports_apply_patch,
      path=json_path.add_key("supports_apply_patch"),
    )
  }
  let access_token : String? = {
    guard json.get("access_token") is Some(access_token) else { None }
    Some(@json.from_json(access_token, path=json_path.add_key("access_token")))
  }
  let account_id : String? = {
    guard json.get("account_id") is Some(account_id) else { None }
    Some(@json.from_json(account_id, path=json_path.add_key("account_id")))
  }
  let refresh_token : String? = {
    guard json.get("refresh_token") is Some(refresh_token) else { None }
    Some(
      @json.from_json(refresh_token, path=json_path.add_key("refresh_token")),
    )
  }
  let id_token : String? = {
    guard json.get("id_token") is Some(id_token) else { None }
    Some(@json.from_json(id_token, path=json_path.add_key("id_token")))
  }
  {
    name,
    description,
    model_name,
    model_type,
    api_key,
    base_url,
    safe_zone_tokens,
    supports_anthropic_prompt_caching,
    supports_apply_patch,
    access_token,
    account_id,
    refresh_token,
    id_token,
  }
}

///|
pub impl @json.FromJson for Type with fn from_json(
  json : Json,
  json_path : @json.JsonPath,
) -> Type {
  guard json is String(json) else {
    raise JsonDecodeError(
      (json_path, "@vectie/moonclaw/model.Type::from_json: expected string"),
    )
  }
  match json {
    "saas/openai" => SaaS(OpenAI)
    "saas/anthropic" => SaaS(Anthropic)
    "saas/codex_oauth" => SaaS(CodexOAuth)
    "saas/copilot" => SaaS(Copilot)
    "saas/qwen" => SaaS(Qwen)
    "saas/kimi" => SaaS(Kimi)
    "saas/kimi_coding" => SaaS(KimiCoding)
    [.. "saas/", .. provider] =>
      raise JsonDecodeError(
        (
          json_path,
          "@vectie/moonclaw/model.Type::from_json: unsupported SaaS provider '\{provider}'",
        ),
      )
    _ =>
      raise JsonDecodeError(
        (
          json_path,
          "@vectie/moonclaw/model.Type::from_json: unknown model type '\{json}'",
        ),
      )
  }
}

///|
pub impl ToJson for Type with fn to_json(self) {
  match self {
    SaaS(OpenAI) => "saas/openai"
    SaaS(Anthropic) => "saas/anthropic"
    SaaS(CodexOAuth) => "saas/codex_oauth"
    SaaS(Copilot) => "saas/copilot"
    SaaS(Qwen) => "saas/qwen"
    SaaS(Kimi) => "saas/kimi"
    SaaS(KimiCoding) => "saas/kimi_coding"
  }
}