///|
pub let status_open = "open"

///|
pub let status_in_progress = "in_progress"

///|
pub let status_blocked = "blocked"

///|
pub let status_closed = "closed"

///|
pub(all) struct Issue {
  id : String
  title : String
  description : String
  status : String
  priority : Int
  created_at : UInt64
  updated_at : UInt64
  deps : Array[String]
  notes : String
} derive(ToJson, Show, Eq)

///|
fn[T : @json.FromJson] get_field(
  obj : Map[String, Json],
  path : @json.JsonPath,
  key : String,
) -> T raise @json.JsonDecodeError {
  match obj.get(key) {
    Some(value) => @json.from_json(value, path=path.add_key(key))
    None => raise @json.JsonDecodeError((path.add_key(key), "missing field"))
  }
}

///|
fn get_optional_string(
  obj : Map[String, Json],
  path : @json.JsonPath,
  key : String,
) -> String raise @json.JsonDecodeError {
  match obj.get(key) {
    Some(value) => @json.from_json(value, path=path.add_key(key))
    None => ""
  }
}

///|
pub impl @json.FromJson for Issue with from_json(json, path) {
  match json {
    Object(obj) => {
      let id : String = get_field(obj, path, "id")
      let title : String = get_field(obj, path, "title")
      let description : String = get_optional_string(obj, path, "description")
      let status : String = get_field(obj, path, "status")
      let priority : Int = get_field(obj, path, "priority")
      let created_at : UInt64 = get_field(obj, path, "created_at")
      let updated_at : UInt64 = get_field(obj, path, "updated_at")
      let deps : Array[String] = get_field(obj, path, "deps")
      let notes : String = get_field(obj, path, "notes")
      Issue::{
        id,
        title,
        description,
        status,
        priority,
        created_at,
        updated_at,
        deps,
        notes,
      }
    }
    _ =>
      raise @json.JsonDecodeError((path, "Issue::from_json: expected object"))
  }
}

///|
pub fn normalize_status(value : String) -> String? {
  let cleaned = value.trim().to_lower()
  match cleaned {
    "open" => Some(status_open)
    "in_progress" | "in-progress" | "inprogress" => Some(status_in_progress)
    "blocked" => Some(status_blocked)
    "closed" | "done" => Some(status_closed)
    _ => None
  }
}

///|
pub fn is_ready(issue : Issue, status_by_id : Map[String, String]) -> Bool {
  if issue.status != status_open && issue.status != status_in_progress {
    return false
  }
  if issue.deps.length() == 0 {
    return true
  }
  for dep in issue.deps {
    match status_by_id.get(dep) {
      Some(status) => if status != status_closed { return false }
      None => return false
    }
  }
  true
}