///|
pub suberror VectorError {
  DimensionMismatch(String)
  EmptyVector
  InvalidK
  ClusterError(String)
  IndexError(String)
}

///|
pub impl Show for VectorError with fn output(self, logger) {
  match self {
    DimensionMismatch(s) => logger.write_string("DimensionMismatch: " + s)
    EmptyVector => logger.write_string("EmptyVector")
    InvalidK => logger.write_string("InvalidK")
    ClusterError(s) => logger.write_string("ClusterError: " + s)
    IndexError(s) => logger.write_string("IndexError: " + s)
  }
}

///|
pub(all) struct Document {
  id : String
  vector : Array[Double]
  metadata : Array[(String, String)]
}

///|
pub fn Document::new(
  id : String,
  vector : Array[Double],
  metadata : Array[(String, String)],
) -> Document {
  { id, vector, metadata }
}

///|
pub(all) enum DistanceMetric {
  Cosine
  Euclidean
  Manhattan
  DotProduct
}

///|
pub fn dot_product(
  v1 : Array[Double],
  v2 : Array[Double],
) -> Double raise VectorError {
  if v1.length() != v2.length() {
    raise DimensionMismatch(
      "Vector dimensions do not match: " +
      v1.length().to_string() +
      " vs " +
      v2.length().to_string(),
    )
  }
  if v1.length() == 0 {
    raise EmptyVector
  }
  let mut sum = 0.0
  for i = 0; i < v1.length(); i = i + 1 {
    sum = sum + v1[i] * v2[i]
  }
  sum
}

///|
pub fn euclidean_distance(
  v1 : Array[Double],
  v2 : Array[Double],
) -> Double raise VectorError {
  if v1.length() != v2.length() {
    raise DimensionMismatch(
      "Vector dimensions do not match: " +
      v1.length().to_string() +
      " vs " +
      v2.length().to_string(),
    )
  }
  if v1.length() == 0 {
    raise EmptyVector
  }
  let mut sum = 0.0
  for i = 0; i < v1.length(); i = i + 1 {
    let diff = v1[i] - v2[i]
    sum = sum + diff * diff
  }
  sum.sqrt()
}

///|
pub fn manhattan_distance(
  v1 : Array[Double],
  v2 : Array[Double],
) -> Double raise VectorError {
  if v1.length() != v2.length() {
    raise DimensionMismatch(
      "Vector dimensions do not match: " +
      v1.length().to_string() +
      " vs " +
      v2.length().to_string(),
    )
  }
  if v1.length() == 0 {
    raise EmptyVector
  }
  let mut sum = 0.0
  for i = 0; i < v1.length(); i = i + 1 {
    let diff = v1[i] - v2[i]
    let abs_diff = if diff < 0.0 { -diff } else { diff }
    sum = sum + abs_diff
  }
  sum
}

///|
pub fn cosine_similarity(
  v1 : Array[Double],
  v2 : Array[Double],
) -> Double raise VectorError {
  let dot = dot_product(v1, v2)
  let mut norm1 = 0.0
  let mut norm2 = 0.0
  for i = 0; i < v1.length(); i = i + 1 {
    norm1 = norm1 + v1[i] * v1[i]
    norm2 = norm2 + v2[i] * v2[i]
  }
  if norm1 == 0.0 || norm2 == 0.0 {
    return 0.0
  }
  dot / (norm1.sqrt() * norm2.sqrt())
}

///|
pub fn calculate_distance(
  v1 : Array[Double],
  v2 : Array[Double],
  metric : DistanceMetric,
) -> Double raise VectorError {
  match metric {
    Cosine => cosine_similarity(v1, v2)
    Euclidean => euclidean_distance(v1, v2)
    Manhattan => manhattan_distance(v1, v2)
    DotProduct => dot_product(v1, v2)
  }
}

///|
fn find_str(s : String, sub : String, start : Int) -> Int {
  let len_s = s.length()
  let len_sub = sub.length()
  if len_sub == 0 {
    return start
  }
  for i = start; i <= len_s - len_sub; i = i + 1 {
    let mut match_found = true
    for j = 0; j < len_sub; j = j + 1 {
      if s[i + j] != sub[j] {
        match_found = false
        break
      }
    }
    if match_found {
      return i
    }
  }
  -1
}

///|
fn parse_double(s : String) -> Double {
  let trimmed = s.trim().to_owned()
  let len = trimmed.length()
  if len == 0 {
    return 0.0
  }
  let mut val = 0.0
  let mut is_neg = false
  let mut idx = 0
  if trimmed[0] == '-' {
    is_neg = true
    idx = 1
  } else if trimmed[0] == '+' {
    idx = 1
  }

  while idx < len && trimmed[idx] >= '0' && trimmed[idx] <= '9' {
    let digit = (trimmed[idx].to_int() - 48).to_double()
    val = val * 10.0 + digit
    idx = idx + 1
  }

  if idx < len && trimmed[idx] == '.' {
    idx = idx + 1
    let mut factor = 0.1
    while idx < len && trimmed[idx] >= '0' && trimmed[idx] <= '9' {
      let digit = (trimmed[idx].to_int() - 48).to_double()
      val = val + digit * factor
      factor = factor * 0.1
      idx = idx + 1
    }
  }

  if is_neg {
    -val
  } else {
    val
  }
}

///|
pub fn Document::to_json(self : Document) -> String {
  let sb = StringBuilder::new()
  sb.write_string("{\"id\":\"" + self.id + "\",\"vector\":[")
  for i = 0; i < self.vector.length(); i = i + 1 {
    if i > 0 {
      sb.write_string(",")
    }
    sb.write_string(self.vector[i].to_string())
  }
  sb.write_string("],\"metadata\":[")
  for i = 0; i < self.metadata.length(); i = i + 1 {
    if i > 0 {
      sb.write_string(",")
    }
    sb.write_string(
      "[\"" + self.metadata[i].0 + "\",\"" + self.metadata[i].1 + "\"]",
    )
  }
  sb.write_string("]}")
  sb.to_string()
}

///|
pub fn Document::from_json(json : String) -> Document raise VectorError {
  let id_tag = "\"id\":\""
  let id_idx = find_str(json, id_tag, 0)
  if id_idx == -1 {
    raise IndexError("Missing id field in JSON")
  }
  let id_start = id_idx + id_tag.length()
  let id_end = find_str(json, "\"", id_start)
  if id_end == -1 {
    raise IndexError("Mismatched quote for id in JSON")
  }
  let id = json[id_start:id_end].to_owned()

  let vec_tag = "\"vector\":["
  let vec_idx = find_str(json, vec_tag, 0)
  if vec_idx == -1 {
    raise IndexError("Missing vector field in JSON")
  }
  let vec_start = vec_idx + vec_tag.length()
  let vec_end = find_str(json, "]", vec_start)
  if vec_end == -1 {
    raise IndexError("Missing closing bracket for vector in JSON")
  }
  let vec_content = json[vec_start:vec_end].to_owned()
  let vector = []
  if vec_content.trim().to_owned().length() > 0 {
    let parts = vec_content.split(",")
    for p in parts {
      vector.push(parse_double(p.to_owned()))
    }
  }

  let meta_tag = "\"metadata\":["
  let meta_idx = find_str(json, meta_tag, 0)
  if meta_idx == -1 {
    raise IndexError("Missing metadata field in JSON")
  }
  let meta_start = meta_idx + meta_tag.length()
  let metadata = []
  let mut search_idx = meta_start
  while true {
    let sub_start_idx = find_str(json, "[\"", search_idx)
    if sub_start_idx == -1 || sub_start_idx > json.length() - 5 {
      break
    }
    let key_start = sub_start_idx + 2
    let key_end = find_str(json, "\"", key_start)
    if key_end == -1 {
      break
    }
    let key = json[key_start:key_end].to_owned()

    let val_sep = "\",\""
    let val_sep_idx = find_str(json, val_sep, key_end)
    if val_sep_idx == -1 {
      break
    }
    let val_start = val_sep_idx + val_sep.length()
    let val_end = find_str(json, "\"", val_start)
    if val_end == -1 {
      break
    }
    let val = json[val_start:val_end].to_owned()
    metadata.push((key, val))

    search_idx = val_end + 2
  }

  { id, vector, metadata }
}