///|
/// Source code location
pub(all) struct Loc {
  /// Package path
  path : String
  /// File name
  file : String
  /// Line number
  line : Int
  /// Column number
  column : Int
} derive(Show, ToJson, FromJson, Eq)

///|
/// Type path, represents fully qualified name
pub(all) struct TypePath {
  /// Package path
  path : String
  /// Type name
  name : String
} derive(Show, ToJson, FromJson, Eq)

///|
/// Semantic type, represents complete type information
pub(all) enum Stype {
  /// Constructor type, e.g. Array[Int]
  Constr(constr~ : TypePath, arguments~ : Array[Stype])
  /// Arrow/function type
  Arrow(
    parameters~ : Array[Stype],
    return_type~ : Stype,
    error_type~ : Stype?,
    is_async~ : Bool
  )
  /// Type parameter, e.g. T
  Param(name~ : String)
  /// Simple type path
  Path(path~ : String, name~ : String)
} derive(Show, Eq)

///|
/// Custom ToJson for Stype to output format with "kind" field
pub impl ToJson for Stype with to_json(self) {
  match self {
    Constr(constr~, arguments~) => {
      let obj : Map[String, Json] = {}
      obj["kind"] = Json::string("constr")
      obj["constructor"] = constr.to_json()
      obj["arguments"] = Json::array(arguments.map(fn(s) { s.to_json() }))
      Json::object(obj)
    }
    Arrow(parameters~, return_type~, error_type~, is_async~) => {
      let obj : Map[String, Json] = {}
      obj["kind"] = Json::string("arrow")
      obj["parameters"] = Json::array(parameters.map(fn(s) { s.to_json() }))
      obj["return_type"] = return_type.to_json()
      match error_type {
        Some(e) => obj["error_type"] = e.to_json()
        None => obj["error_type"] = Json::null()
      }
      obj["is_async"] = Json::boolean(is_async)
      Json::object(obj)
    }
    Param(name~) => {
      let obj : Map[String, Json] = {}
      obj["kind"] = Json::string("param")
      obj["name"] = Json::string(name)
      Json::object(obj)
    }
    Path(path~, name~) => {
      let obj : Map[String, Json] = {}
      obj["kind"] = Json::string("path")
      obj["path"] = Json::string(path)
      obj["name"] = Json::string(name)
      Json::object(obj)
    }
  }
}

///|
/// Custom FromJson for Stype to handle JSON format with "kind" field
/// Also handles untagged Path variant (no "kind" field, just "name" and "path")
pub impl @json.FromJson for Stype with from_json(json, path) {
  let obj = match json {
    Object(m) => m
    _ => raise @json.JsonDecodeError((path, "Expected object for Stype"))
  }
  // Check for "kind" field - if missing, assume it's an untagged Path
  let kind : String = match obj.get("kind") {
    Some(String(s)) => s
    _ => "path" // Default to path for untagged variant
  }
  match kind {
    "constr" => {
      let constr : TypePath = @json.from_json(
        obj.get("constructor").unwrap_or(Json::null()),
        path=path.add_key("constructor"),
      )
      let arguments : Array[Stype] = @json.from_json(
        obj.get("arguments").unwrap_or(Json::array([])),
        path=path.add_key("arguments"),
      )
      Stype::Constr(constr~, arguments~)
    }
    "arrow" => {
      let parameters : Array[Stype] = @json.from_json(
        obj.get("parameters").unwrap_or(Json::array([])),
        path=path.add_key("parameters"),
      )
      let return_type : Stype = @json.from_json(
        obj.get("return_type").unwrap_or(Json::null()),
        path=path.add_key("return_type"),
      )
      // error_type can be null or object
      let error_type : Stype? = match obj.get("error_type") {
        Some(Null) | None => None
        Some(e) => Some(@json.from_json(e, path=path.add_key("error_type")))
      }
      let is_async : Bool = match obj.get("is_async") {
        Some(True) => true
        _ => false
      }
      Stype::Arrow(parameters~, return_type~, error_type~, is_async~)
    }
    "param" => {
      let name : String = match obj.get("name") {
        Some(String(s)) => s
        _ => raise @json.JsonDecodeError((path, "Missing 'name' for param"))
      }
      Stype::Param(name~)
    }
    "path" | _ => {
      // Handle both explicit "path" kind and untagged variant
      let type_path : String = match obj.get("path") {
        Some(String(s)) => s
        _ => ""
      }
      let name : String = match obj.get("name") {
        Some(String(s)) => s
        _ => raise @json.JsonDecodeError((path, "Missing 'name' for path"))
      }
      Stype::Path(path=type_path, name~)
    }
  }
}

///|
/// Module/package metadata (from JSON)
pub(all) struct MetaInfo {
  /// Full name, e.g. moonbitlang/core
  name : String
  /// Version
  version : String
  /// License
  license : String?
  /// Keywords
  keywords : Array[String]
  /// Repository URL
  repository : String?
} derive(Show, ToJson, FromJson, Eq)

///|
/// Resource from JSON (flattened structure)
pub(all) struct Resource {
  /// Resource kind: "module", "package", or "module_and_package"
  kind : String
  /// README content
  readme_content : String
  /// Meta info (for module types)
  meta_info : MetaInfo?
  /// Module index path (for module types)
  module_index : String?
  /// Package data path (for package types)
  package_data : String?
  /// Source files (for package types)
  source_files : Array[String]?
  /// Parent module path (for package types)
  module_path : String?
} derive(Show, ToJson, FromJson, Eq)

///|
/// Value/function documentation
pub(all) struct ValueDoc {
  /// Name
  name : String
  /// Documentation string
  docstring : String
  /// Function signature (HTML format)
  signature : String
  /// Source location
  loc : Loc
} derive(Show, ToJson, FromJson, Eq)

///|
/// Implementation documentation
pub(all) struct ImplDoc {
  /// Implemented type
  self_ : Stype
  /// Implemented trait
  trait_ : TypePath
  /// Method list
  methods : Array[ValueDoc]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Type documentation
pub(all) struct TypeDoc {
  /// Type name
  name : String
  /// Documentation string
  docstring : String
  /// Type signature (HTML format)
  signature : String
  /// Source location
  loc : Loc
  /// Method list
  methods : Array[ValueDoc]
  /// Implementation list
  impls : Array[ImplDoc]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Trait documentation
pub(all) struct TraitDoc {
  /// Trait name
  name : String
  /// Documentation string
  docstring : String
  /// Trait signature (HTML format)
  signature : String
  /// Source location
  loc : Loc
  /// Implementation list
  impls : Array[ImplDoc]
  /// Object type info (optional)
  object_type_info : MiscDoc?
} derive(Show, ToJson, FromJson, Eq)

///|
/// Type alias documentation
pub(all) struct TypeAliasDoc {
  /// Alias name
  name : String
  /// Documentation string
  docstring : String
  /// Alias signature (HTML format)
  signature : String
  /// Source location
  loc : Loc
} derive(Show, ToJson, FromJson, Eq)

///|
/// Error type documentation
pub(all) struct ErrorDoc {
  /// Error name
  name : String
  /// Documentation string
  docstring : String
  /// Error signature (HTML format)
  signature : String
  /// Source location
  loc : Loc
  /// Implementation list
  impls : Array[ImplDoc]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Miscellaneous documentation (e.g. extension methods)
pub(all) struct MiscDoc {
  /// Name
  name : String
  /// Implementation list
  impls : Array[ImplDoc]
  /// Method list
  methods : Array[ValueDoc]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Complete package documentation data
pub(all) struct PackageData {
  /// Package name
  name : String
  /// Type list
  types : Array[TypeDoc]
  /// Trait list
  traits : Array[TraitDoc]
  /// Type alias list (renamed from "typealias" in JSON)
  type_aliases : Array[TypeAliasDoc]
  /// Error type list
  errors : Array[ErrorDoc]
  /// Value/function list
  values : Array[ValueDoc]
  /// Miscellaneous (e.g. extension methods)
  misc : Array[MiscDoc]
} derive(Show, ToJson, Eq)

///|
/// Custom FromJson for PackageData to handle "typealias" field name
pub impl @json.FromJson for PackageData with from_json(json, path) {
  let obj = match json {
    Object(m) => m
    _ => raise @json.JsonDecodeError((path, "Expected object for PackageData"))
  }
  let name : String = @json.from_json(
    obj.get("name").unwrap_or(Json::string("")),
    path=path.add_key("name"),
  )
  let types : Array[TypeDoc] = @json.from_json(
    obj.get("types").unwrap_or(Json::array([])),
    path=path.add_key("types"),
  )
  let traits : Array[TraitDoc] = @json.from_json(
    obj.get("traits").unwrap_or(Json::array([])),
    path=path.add_key("traits"),
  )
  // Handle "typealias" -> "type_aliases"
  let type_aliases : Array[TypeAliasDoc] = @json.from_json(
    obj.get("typealias").unwrap_or(Json::array([])),
    path=path.add_key("typealias"),
  )
  let errors : Array[ErrorDoc] = @json.from_json(
    obj.get("errors").unwrap_or(Json::array([])),
    path=path.add_key("errors"),
  )
  let values : Array[ValueDoc] = @json.from_json(
    obj.get("values").unwrap_or(Json::array([])),
    path=path.add_key("values"),
  )
  let misc : Array[MiscDoc] = @json.from_json(
    obj.get("misc").unwrap_or(Json::array([])),
    path=path.add_key("misc"),
  )
  PackageData::{ name, types, traits, type_aliases, errors, values, misc }
}

///|
/// Implementation index
pub(all) struct ImplIndex {
  /// Implemented type
  self_ : Stype
  /// Implemented trait
  trait_ : TypePath
  /// Method name list
  methods : Array[String]
} derive(Show, ToJson, Eq)

///|
/// Custom FromJson for ImplIndex to handle "self" and "trait" field names
pub impl @json.FromJson for ImplIndex with from_json(json, path) {
  let obj = match json {
    Object(m) => m
    _ => raise @json.JsonDecodeError((path, "Expected object for ImplIndex"))
  }
  let self_ : Stype = @json.from_json(
    obj.get("self").unwrap_or(Json::null()),
    path=path.add_key("self"),
  )
  let trait_ : TypePath = @json.from_json(
    obj.get("trait").unwrap_or(Json::null()),
    path=path.add_key("trait"),
  )
  let methods : Array[String] = @json.from_json(
    obj.get("methods").unwrap_or(Json::array([])),
    path=path.add_key("methods"),
  )
  ImplIndex::{ self_, trait_, methods }
}

///|
/// Trait index
pub(all) struct TraitIndex {
  /// Trait name
  name : String
  /// Implementation index list
  impls : Array[ImplIndex]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Type index
pub(all) struct TypeIndex {
  /// Type name
  name : String
  /// Implementation index list
  impls : Array[ImplIndex]
  /// Method name list
  methods : Array[String]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Miscellaneous index
pub(all) struct MiscIndex {
  /// Name
  name : String
  /// Implementation index list
  impls : Array[ImplIndex]
  /// Method name list
  methods : Array[String]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Package index
pub(all) struct PackageIndex {
  /// Package path
  path : String
  /// Trait index list
  traits : Array[TraitIndex]
  /// Type index list
  types : Array[TypeIndex]
  /// Type alias name list (renamed from "typealias" in JSON)
  type_aliases : Array[String]
  /// Value name list
  values : Array[String]
  /// Miscellaneous index list
  misc : Array[MiscIndex]
} derive(Show, ToJson, Eq)

///|
/// Custom FromJson for PackageIndex to handle "typealias" field name
pub impl @json.FromJson for PackageIndex with from_json(json, path) {
  let obj = match json {
    Object(m) => m
    _ => raise @json.JsonDecodeError((path, "Expected object for PackageIndex"))
  }
  let pkg_path : String = @json.from_json(
    obj.get("path").unwrap_or(Json::string("")),
    path=path.add_key("path"),
  )
  let traits : Array[TraitIndex] = @json.from_json(
    obj.get("traits").unwrap_or(Json::array([])),
    path=path.add_key("traits"),
  )
  let types : Array[TypeIndex] = @json.from_json(
    obj.get("types").unwrap_or(Json::array([])),
    path=path.add_key("types"),
  )
  let type_aliases : Array[String] = @json.from_json(
    obj.get("typealias").unwrap_or(Json::array([])),
    path=path.add_key("typealias"),
  )
  let values : Array[String] = @json.from_json(
    obj.get("values").unwrap_or(Json::array([])),
    path=path.add_key("values"),
  )
  let misc : Array[MiscIndex] = @json.from_json(
    obj.get("misc").unwrap_or(Json::array([])),
    path=path.add_key("misc"),
  )
  PackageIndex::{ path: pkg_path, traits, types, type_aliases, values, misc }
}

///|
/// Index node (tree structure)
pub(all) struct IndexNode {
  /// Node name
  name : String
  /// Package index (optional)
  pkg : PackageIndex?
  /// Child node list
  childs : Array[IndexNode]
} derive(Show, ToJson, Eq)

///|
/// Custom FromJson for IndexNode to handle "package" field name
pub impl @json.FromJson for IndexNode with from_json(json, path) {
  let obj = match json {
    Object(m) => m
    _ => raise @json.JsonDecodeError((path, "Expected object for IndexNode"))
  }
  let name : String = @json.from_json(
    obj.get("name").unwrap_or(Json::string("")),
    path=path.add_key("name"),
  )
  // Handle "package" -> "pkg", where package can be null or an object
  let pkg : PackageIndex? = match obj.get("package") {
    Some(Null) | None => None
    Some(pkg_json) =>
      Some(@json.from_json(pkg_json, path=path.add_key("package")))
  }
  let childs : Array[IndexNode] = @json.from_json(
    obj.get("childs").unwrap_or(Json::array([])),
    path=path.add_key("childs"),
  )
  IndexNode::{ name, pkg, childs }
}

///|
/// Module summary (for list display)
pub(all) struct ModuleSummary {
  /// Module name
  name : String
  /// Version
  version : String
  /// License
  license : String
  /// Repository URL
  repository : String
  /// Keywords
  keywords : Array[String]
  /// Description
  description : String
  /// Whether new module
  is_new : Bool
  /// Created time
  created_at : String
} derive(Show, ToJson, FromJson, Eq)

///|
/// Statistics
pub(all) struct Statistics {
  /// Total modules
  total_modules : Int
  /// Total packages
  total_packages : Int
  /// Total lines of code
  total_lines : Int
} derive(Show, ToJson, FromJson, Eq)

///|
/// User module info
pub(all) struct UserModule {
  /// Module name
  name : String
  /// Version
  version : String
} derive(Show, ToJson, FromJson, Eq)

///|
/// User info
pub(all) struct UserInfo {
  /// Username
  username : String
  /// Avatar URL
  avatar_url : String?
  /// Published modules list
  modules : Array[UserModule]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Full module data including all packages
pub(all) struct FullModuleData {
  /// Module path
  path : String
  /// Module resource
  resource : Resource
  /// Module index
  index : IndexNode
  /// All package data, keyed by package path
  packages : Map[String, PackageData]
} derive(Show, ToJson, FromJson, Eq)

///|
/// Search entry for fuzzy search
pub(all) struct SearchEntry {
  /// Display name
  name : String
  /// Full path
  path : String
  /// Entry kind (type, trait, value, etc.)
  kind : String
  /// Signature (optional)
  signature : String?
} derive(Show, ToJson, FromJson, Eq)