///|
pub(all) struct MorphAnalysis {
  word : String
  stem : String?
  prefix : String?
  suffix : String?
  flags : String
  morphology : String
}

///|
pub(all) struct Explanation {
  accepted : Bool
  steps : Array[String]
  suggestions : Array[Suggestion]
}

///|
pub fn analyze(
  dictionary : Dictionary,
  config : AffixConfig,
  word : String,
) -> Array[MorphAnalysis] {
  let normalized = apply_iconv(config, apply_ignore(config, word))
  let dictionary = normalized_dictionary(config, dictionary)
  let result = spell(dictionary, config, normalized)
  let analyses : Array[MorphAnalysis] = []
  if !result.accepted {
    return analyses
  }
  match result.stem {
    Some(stem) => {
      let lower = language_lower(config, stem)
      for entry in dictionary.entries {
        if language_lower(config, entry.word) == lower {
          analyses.push({
            word: normalized,
            stem: Some(entry.word),
            prefix: result.prefix,
            suffix: result.suffix,
            flags: entry.flags,
            morphology: entry.morph,
          })
        }
      }
      if analyses.length() == 0 {
        analyses.push({
          word: normalized,
          stem: Some(stem),
          prefix: result.prefix,
          suffix: result.suffix,
          flags: "",
          morphology: "",
        })
      }
    }
    None =>
      analyses.push({
        word: normalized,
        stem: Some(normalized),
        prefix: None,
        suffix: None,
        flags: "",
        morphology: "",
      })
  }
  analyses
}

///|
pub fn stem(
  dictionary : Dictionary,
  config : AffixConfig,
  word : String,
) -> String? {
  let analyses = analyze(dictionary, config, word)
  for item in analyses {
    match item.stem {
      Some(value) => return Some(value)
      None => ()
    }
  }
  None
}

///|
pub fn explain(
  dictionary : Dictionary,
  config : AffixConfig,
  word : String,
  suggestion_limit : Int,
) -> Explanation {
  let normalized = apply_iconv(config, apply_ignore(config, word))
  let dictionary = normalized_dictionary(config, dictionary)
  let result = spell(dictionary, config, normalized)
  let steps : Array[String] = []
  steps.push("normalize: " + word + " -> " + normalized)
  if result.accepted {
    steps.push("lookup: accepted")
    match result.stem {
      Some(value) => steps.push("stem: " + value)
      None => steps.push("stem: compound or phrase")
    }
    match result.prefix {
      Some(value) => steps.push("prefix: " + value)
      None => ()
    }
    match result.suffix {
      Some(value) => steps.push("suffix: " + value)
      None => ()
    }
    { accepted: true, steps, suggestions: [], }
  } else {
    steps.push("lookup: rejected")
    {
      accepted: false,
      steps,
      suggestions: suggest(dictionary, config, normalized, suggestion_limit),
    }
  }
}

///|
fn json_escape(text : String) -> String {
  let builder = StringBuilder()
  for ch in text {
    match ch {
      '"' => builder.write_string("\\\"")
      '\\' => builder.write_string("\\\\")
      '\n' => builder.write_string("\\n")
      '\r' => builder.write_string("\\r")
      '\t' => builder.write_string("\\t")
      _ =>
        if ch.to_int() < 0x20 {
          builder.write_string("\\u")
          let value = ch.to_int()
          let digits = "0123456789ABCDEF"
          builder.write_char(digits.get_char(value / 0x1000 % 0x10).unwrap())
          builder.write_char(digits.get_char(value / 0x100 % 0x10).unwrap())
          builder.write_char(digits.get_char(value / 0x10 % 0x10).unwrap())
          builder.write_char(digits.get_char(value % 0x10).unwrap())
        } else {
          builder.write_char(ch)
        }
    }
  }
  builder.to_string()
}

///|
pub fn analyses_to_json(items : Array[MorphAnalysis]) -> String {
  let builder = StringBuilder()
  builder.write_char('[')
  for i in 0.. 0 {
      builder.write_char(',')
    }
    let item = items[i]
    builder.write_string("{\"word\":\"")
    builder.write_string(json_escape(item.word))
    builder.write_string("\",\"stem\":")
    match item.stem {
      Some(value) => builder.write_string("\"" + json_escape(value) + "\"")
      None => builder.write_string("null")
    }
    builder.write_string(",\"prefix\":")
    match item.prefix {
      Some(value) => builder.write_string("\"" + json_escape(value) + "\"")
      None => builder.write_string("null")
    }
    builder.write_string(",\"suffix\":")
    match item.suffix {
      Some(value) => builder.write_string("\"" + json_escape(value) + "\"")
      None => builder.write_string("null")
    }
    builder.write_string(",\"flags\":\"" + json_escape(item.flags) + "\"")
    builder.write_string(
      ",\"morphology\":\"" + json_escape(item.morphology) + "\"}",
    )
  }
  builder.write_char(']')
  builder.to_string()
}

///|
pub fn explanation_to_json(item : Explanation) -> String {
  let builder = StringBuilder()
  builder.write_string("{\"accepted\":")
  builder.write_string(item.accepted.to_string())
  builder.write_string(",\"steps\":[")
  for i in 0.. 0 {
      builder.write_char(',')
    }
    builder.write_string("\"" + json_escape(item.steps[i]) + "\"")
  }
  builder.write_string("],\"suggestions\":[")
  for i in 0.. 0 {
      builder.write_char(',')
    }
    let suggestion = item.suggestions[i]
    builder.write_string(
      "{\"word\":\"" +
      json_escape(suggestion.word) +
      "\",\"distance\":" +
      suggestion.distance.to_string() +
      "}",
    )
  }
  builder.write_string("]}")
  builder.to_string()
}