///|
/// Default base URL for mooncakes.io
let default_base_url : String = "https://mooncakes.io"

///|
/// Default API URL for mooncakes.io
let default_api_url : String = "https://mooncakes.io/api/v0"

///|
/// HTTP error
pub(all) suberror HttpError {
  /// Network error
  NetworkError(String)
  /// JSON parse error
  ParseError(String)
  /// HTTP status error
  StatusError(code~ : Int, message~ : String)
} derive(Show)

///|
/// Fetch JSON from URL and parse it
pub async fn[T : FromJson] fetch_json(url : String) -> T {
  let result = @http.get(url)
  let response = result.0
  let body = result.1
  if response.code != 200 {
    raise HttpError::StatusError(code=response.code, message=response.reason)
  }
  let text = body.text()
  let json : Json = @json.parse(text) catch {
    e => raise HttpError::ParseError("Failed to parse JSON: \{e}")
  }
  @json.from_json(json) catch {
    e => raise HttpError::ParseError("Failed to deserialize: \{e}")
  }
}

///|
/// Fetch text from URL
pub async fn fetch_text(url : String) -> String {
  let result = @http.get(url)
  let response = result.0
  let body = result.1
  if response.code != 200 {
    raise HttpError::StatusError(code=response.code, message=response.reason)
  }
  body.text()
}

///|
/// Mooncakes API client
pub(all) struct MooncakesClient {
  /// Base URL for static assets
  base_url : String
  /// API URL
  api_url : String
}

///|
/// Create a new MooncakesClient with default URLs
pub fn MooncakesClient::new() -> MooncakesClient {
  MooncakesClient::{ base_url: default_base_url, api_url: default_api_url }
}

///|
/// Create a new MooncakesClient with custom URLs
pub fn MooncakesClient::with_urls(
  base_url? : String = default_base_url,
  api_url? : String = default_api_url,
) -> MooncakesClient {
  MooncakesClient::{ base_url, api_url }
}

///|
/// Get recent modules
pub async fn MooncakesClient::get_recent_modules(
  self : MooncakesClient,
) -> Array[ModuleSummary] {
  fetch_json("\{self.api_url}/modules/all")
}

///|
/// Get statistics
pub async fn MooncakesClient::get_statistics(
  self : MooncakesClient,
) -> Statistics {
  fetch_json("\{self.api_url}/modules/statistics")
}

///|
/// Get user info
pub async fn MooncakesClient::get_user(
  self : MooncakesClient,
  username : String,
) -> UserInfo {
  fetch_json("\{self.api_url}/users/\{username}")
}

///|
/// Get resource for a module or package
pub async fn MooncakesClient::get_resource(
  self : MooncakesClient,
  path : String,
) -> Resource {
  fetch_json("\{self.base_url}/assets/\{path}/resource.json")
}

///|
/// Get package data
pub async fn MooncakesClient::get_package_data(
  self : MooncakesClient,
  path : String,
) -> PackageData {
  fetch_json("\{self.base_url}/assets/\{path}/package_data.json")
}

///|
/// Get module index
pub async fn MooncakesClient::get_module_index(
  self : MooncakesClient,
  path : String,
) -> IndexNode {
  fetch_json("\{self.base_url}/assets/\{path}/module_index.json")
}

///|
/// Get README content
pub async fn MooncakesClient::get_readme(
  self : MooncakesClient,
  path : String,
) -> String {
  let resource : Resource = self.get_resource(path)
  resource.readme_content
}

///|
/// Batch fetch package data for multiple paths
pub async fn MooncakesClient::get_package_data_batch(
  self : MooncakesClient,
  paths : Array[String],
) -> Map[String, PackageData] {
  let results : Map[String, PackageData] = {}
  for path in paths {
    let data = self.get_package_data(path)
    results[path] = data
  }
  results
}

///|
/// Get full module data including all packages
pub async fn MooncakesClient::get_full_module_data(
  self : MooncakesClient,
  path : String,
) -> FullModuleData {
  let resource = self.get_resource(path)
  let index = self.get_module_index(path)
  let all_paths = get_all_package_paths(index)
  let packages = self.get_package_data_batch(all_paths)
  FullModuleData::{ path, resource, index, packages }
}

///|
/// Convenience function: get recent modules
pub async fn get_recent_modules() -> Array[ModuleSummary] {
  MooncakesClient::new().get_recent_modules()
}

///|
/// Convenience function: get statistics
pub async fn get_statistics() -> Statistics {
  MooncakesClient::new().get_statistics()
}

///|
/// Convenience function: get user info
pub async fn get_user(username : String) -> UserInfo {
  MooncakesClient::new().get_user(username)
}

///|
/// Convenience function: get resource
pub async fn get_resource(path : String) -> Resource {
  MooncakesClient::new().get_resource(path)
}

///|
/// Convenience function: get package data
pub async fn get_package_data(path : String) -> PackageData {
  MooncakesClient::new().get_package_data(path)
}

///|
/// Convenience function: get module index
pub async fn get_module_index(path : String) -> IndexNode {
  MooncakesClient::new().get_module_index(path)
}