use {
  pulldown_cmark::{CowStr, Event, HeadingLevel, Options, Parser, Tag},
  pulldown_cmark_to_cmark::cmark,
  std::{
    collections::{BTreeMap, BTreeSet},
    error::Error,
    fmt::Write,
    fs,
    ops::Deref,
  },
};

type Result<T = ()> = std::result::Result<T, Box<dyn Error>>;

#[derive(Copy, Clone, Debug)]
enum Language {
  English,
  Chinese,
}

impl Language {
  fn code(&self) -> &'static str {
    match self {
      Self::English => "en",
      Self::Chinese => "zh",
    }
  }

  fn suffix(&self) -> &'static str {
    match self {
      Self::English => "",
      Self::Chinese => ".中文",
    }
  }

  fn introduction(&self) -> &'static str {
    match self {
      Self::Chinese => "说明",
      Self::English => "Introduction",
    }
  }
}

#[derive(Debug)]
struct Chapter<'a> {
  level: HeadingLevel,
  events: Vec<Event<'a>>,
  index: usize,
  language: Language,
}

impl Chapter<'_> {
  fn title(&self) -> String {
    if self.index == 0 {
      return self.language.introduction().into();
    }

    self
      .events
      .iter()
      .skip_while(|event| !matches!(event, Event::Start(Tag::Heading(..))))
      .skip(1)
      .take_while(|event| !matches!(event, Event::End(Tag::Heading(..))))
      .filter_map(|event| match event {
        Event::Code(content) | Event::Text(content) => Some(content.deref()),
        _ => None,
      })
      .collect()
  }

  fn filename(&self) -> String {
    slug(&self.title())
  }

  fn markdown(&self) -> Result<String> {
    let mut markdown = String::new();
    cmark(self.events.iter(), &mut markdown)?;
    if self.index == 0 {
      markdown = markdown.split_inclusive('\n').skip(1).collect::<String>();
    }
    Ok(markdown)
  }
}

fn slug(s: &str) -> String {
  let mut slug = String::new();
  for c in s.chars() {
    match c {
      'A'..='Z' => slug.extend(c.to_lowercase()),
      ' ' => slug.push('-'),
      '?' | '.' | '？' | '’' => {}
      _ => slug.push(c),
    }
  }
  slug
}

fn main() -> Result {
  for language in [Language::English, Language::Chinese] {
    let src = format!("book/{}/src", language.code());
    fs::remove_dir_all(&src).ok();
    fs::create_dir(&src)?;

    let txt = fs::read_to_string(format!("README{}.md", language.suffix()))?;

    let mut chapters = vec![Chapter {
      level: HeadingLevel::H1,
      events: Vec::new(),
      index: 0,
      language,
    }];

    for event in Parser::new_ext(&txt, Options::all()) {
      if let Event::Start(Tag::Heading(level @ (HeadingLevel::H2 | HeadingLevel::H3), ..)) = event {
        let index = chapters.last().unwrap().index + 1;
        chapters.push(Chapter {
          level,
          events: Vec::new(),
          index,
          language,
        });
      }
      chapters.last_mut().unwrap().events.push(event);
    }

    let mut links = BTreeMap::new();

    for chapter in &chapters {
      let mut current = None;
      for event in &chapter.events {
        match event {
          Event::Start(Tag::Heading(..)) => current = Some(Vec::new()),
          Event::End(Tag::Heading(level, ..)) => {
            let events = current.unwrap();
            let title = events
              .iter()
              .filter_map(|event| match event {
                Event::Code(content) | Event::Text(content) => Some(content.deref()),
                _ => None,
              })
              .collect::<String>();
            let slug = slug(&title);
            let link = if let HeadingLevel::H1 | HeadingLevel::H2 | HeadingLevel::H3 = level {
              format!("{}.html", chapter.filename())
            } else {
              format!("{}.html#{}", chapter.filename(), slug)
            };
            links.insert(slug, link);
            current = None;
          }
          _ => {
            if let Some(events) = &mut current {
              events.push(event.clone());
            }
          }
        }
      }
    }

    for chapter in &mut chapters {
      for event in &mut chapter.events {
        if let Event::Start(Tag::Link(_, dest, _)) | Event::End(Tag::Link(_, dest, _)) = event {
          if let Some(anchor) = dest.clone().strip_prefix('#') {
            if anchor != "just" {
              *dest = CowStr::Borrowed(&links[anchor]);
            }
          }
        }
      }
    }

    let mut summary = String::new();

    let mut filenames = BTreeSet::new();

    for chapter in chapters {
      let filename = chapter.filename();
      assert!(!filenames.contains(&filename));

      let path = format!("{src}/{filename}.md");
      fs::write(path, chapter.markdown()?)?;
      let indent = match chapter.level {
        HeadingLevel::H1 => 0,
        HeadingLevel::H2 => 1,
        HeadingLevel::H3 => 2,
        HeadingLevel::H4 => 3,
        HeadingLevel::H5 => 4,
        HeadingLevel::H6 => 5,
      };

      write!(
        summary,
        "{}- [{}](",
        " ".repeat(indent * 4),
        chapter.title(),
      )?;

      if chapter.events.len() > 3 {
        write!(summary, "{filename}.md")?;
      }

      writeln!(summary, ")")?;

      filenames.insert(filename);
    }

    fs::write(format!("{src}/SUMMARY.md"), summary).unwrap();
  }

  Ok(())
}
