#[must_use]
pub fn unindent(text: &str) -> String {
  // find line start and end indices
  let mut lines = Vec::new();
  let mut start = 0;
  for (i, c) in text.char_indices() {
    if c == '\n' || i == text.len() - c.len_utf8() {
      let end = i + c.len_utf8();
      lines.push(&text[start..end]);
      start = end;
    }
  }

  let common_indentation = lines
    .iter()
    .filter(|line| !blank(line))
    .copied()
    .map(indentation)
    .fold(
      None,
      |common_indentation, line_indentation| match common_indentation {
        Some(common_indentation) => Some(common(common_indentation, line_indentation)),
        None => Some(line_indentation),
      },
    )
    .unwrap_or("");

  let mut replacements = Vec::with_capacity(lines.len());

  for (i, line) in lines.iter().enumerate() {
    let blank = blank(line);
    let first = i == 0;
    let last = i == lines.len() - 1;

    let replacement = match (blank, first, last) {
      (true, false, false) => "\n",
      (true, _, _) => "",
      (false, _, _) => &line[common_indentation.len()..],
    };

    replacements.push(replacement);
  }

  replacements.into_iter().collect()
}

fn indentation(line: &str) -> &str {
  let i = line
    .char_indices()
    .take_while(|(_, c)| matches!(c, ' ' | '\t'))
    .map(|(i, _)| i + 1)
    .last()
    .unwrap_or(0);

  &line[..i]
}

fn blank(line: &str) -> bool {
  line.chars().all(|c| matches!(c, ' ' | '\t' | '\r' | '\n'))
}

fn common<'s>(a: &'s str, b: &'s str) -> &'s str {
  let i = a
    .char_indices()
    .zip(b.chars())
    .take_while(|((_, ac), bc)| ac == bc)
    .map(|((i, c), _)| i + c.len_utf8())
    .last()
    .unwrap_or(0);

  &a[0..i]
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn unindents() {
    assert_eq!(unindent("foo"), "foo");
    assert_eq!(unindent("foo\nbar\nbaz\n"), "foo\nbar\nbaz\n");
    assert_eq!(unindent(""), "");
    assert_eq!(unindent("  foo\n  bar"), "foo\nbar");
    assert_eq!(unindent("  foo\n  bar\n\n"), "foo\nbar\n");

    assert_eq!(
      unindent(
        "
          hello
          bar
        "
      ),
      "hello\nbar\n"
    );

    assert_eq!(unindent("hello\n  bar\n  foo"), "hello\n  bar\n  foo");

    assert_eq!(
      unindent(
        "

          hello
          bar

        "
      ),
      "\nhello\nbar\n\n"
    );
  }

  #[test]
  fn indentations() {
    assert_eq!(indentation(""), "");
    assert_eq!(indentation("foo"), "");
    assert_eq!(indentation("   foo"), "   ");
    assert_eq!(indentation("\t\tfoo"), "\t\t");
    assert_eq!(indentation("\t \t foo"), "\t \t ");
  }

  #[test]
  fn blanks() {
    assert!(blank("       \n"));
    assert!(!blank("       foo\n"));
    assert!(blank("\t\t\n"));
  }

  #[test]
  fn commons() {
    assert_eq!(common("foo", "foobar"), "foo");
    assert_eq!(common("foo", "bar"), "");
    assert_eq!(common("", ""), "");
    assert_eq!(common("", "bar"), "");
  }
}
