///|
/// Metadata read from a `moon.mod` file.
pub(all) struct MoonModMetadata {
  root_dir : String?
  name : String?
  version : String?
  description : String?
  license : String?
  repository : String?
  readme : String?
  keywords : Array[String]
} derive(Debug, Eq)

///|
priv struct MoonModFileMetadata {
  name : String?
  version : String?
  description : String?
  license : String?
  repository : String?
  readme : String?
  keywords : Array[String]?
} derive(FromJson)

///|
fn empty_moon_mod_metadata() -> MoonModMetadata {
  {
    root_dir: None,
    name: None,
    version: None,
    description: None,
    license: None,
    repository: None,
    readme: None,
    keywords: [],
  }
}

///|
/// Reads metadata from `moon.mod` in exactly the specified module root.
/// Missing, unreadable, and malformed manifests produce empty metadata.
pub fn read_moon_mod_metadata(root_dir : String) -> MoonModMetadata {
  let path = join_path_string(root_dir, "moon.mod")
  guard (@mbfs.is_file(path) catch { _ => false }) else {
    return empty_moon_mod_metadata()
  }
  decode_moon_mod_metadata(path, root_dir)
}

///|
/// Reads metadata from the nearest enclosing `moon.mod` file.
/// Missing, unreadable, and malformed manifests produce empty metadata.
pub fn read_nearest_moon_mod_metadata(start_dir : String) -> MoonModMetadata {
  for current = start_dir, depth = 0; depth < 16; {
    let candidate = join_path_string(current, "moon.mod")
    if (@mbfs.is_file(candidate) catch { _ => false }) {
      return decode_moon_mod_metadata(candidate, current)
    }
    let parent = dirname_string(current)
    if parent == current || parent == "" {
      return empty_moon_mod_metadata()
    }
    continue parent, depth + 1
  } nobreak {
    empty_moon_mod_metadata()
  }
}

///|
fn decode_moon_mod_metadata(
  path : String,
  root_dir : String,
) -> MoonModMetadata {
  let text = @mbfs.read_file_to_string(path, encoding="utf8") catch {
    _ => return empty_moon_mod_metadata()
  }
  let (ast, _reports) = @moon_config.parse_moon_mod(name=path, text)
  let metadata : MoonModFileMetadata = @json.from_json(ast.to_json()) catch {
    _ => return empty_moon_mod_metadata()
  }
  {
    root_dir: Some(root_dir),
    name: metadata.name,
    version: metadata.version,
    description: metadata.description,
    license: metadata.license,
    repository: metadata.repository,
    readme: metadata.readme,
    keywords: metadata.keywords.unwrap_or([]),
  }
}