use kurobako_core::Result;
use std::cmp;
use std::io::Write;

#[derive(Debug)]
pub struct MarkdownWriter<'a, W> {
    writer: &'a mut W,
    level: usize,
}
impl<'a, W: Write> MarkdownWriter<'a, W> {
    pub fn new(writer: &'a mut W) -> Self {
        Self::with_level(writer, 0)
    }

    pub fn with_level(writer: &'a mut W, level: usize) -> Self {
        Self { writer, level }
    }

    pub fn heading(&mut self, s: &str) -> Result<MarkdownWriter<W>> {
        for _ in 0..=self.level {
            track_write!(self.writer, "#")?
        }
        track_writeln!(self.writer, " {}\n", s)?;

        Ok(MarkdownWriter {
            writer: self.writer,
            level: self.level + 1,
        })
    }

    pub fn inner_mut(&mut self) -> &mut W {
        self.writer
    }

    pub fn newline(&mut self) -> Result<()> {
        track_writeln!(self.writer)
    }

    pub fn code_block(&mut self, lang: &str, code: &str) -> Result<()> {
        track_writeln!(self.writer, "```{}", lang)?;
        track_writeln!(self.writer, "{}", code)?;
        track_writeln!(self.writer, "```")?;
        Ok(())
    }

    pub fn list(&mut self) -> ListWriter<&mut W> {
        ListWriter::new(self.writer)
    }

    pub fn write_table(&mut self, table: &Table) -> Result<()> {
        let mut widthes = table
            .headers
            .iter()
            .map(|h| h.name.len())
            .collect::<Vec<_>>();

        #[allow(clippy::needless_range_loop)]
        for col in 0..table.headers.len() {
            for row in &table.rows {
                if let Some(item) = row.items.get(col) {
                    widthes[col] = cmp::max(widthes[col], item.len());
                }
            }
        }

        track_write!(self.writer, "|")?;
        for (h, w) in table.headers.iter().zip(widthes.iter().cloned()) {
            let s = match h.align {
                Align::Left => format!(" {:<width$} |", h.name, width = w),
                Align::Center => format!(" {:^width$} |", h.name, width = w),
                Align::Right => format!(" {:>width$} |", h.name, width = w),
            };
            track_write!(self.writer, "{}", s)?;
        }
        track!(self.newline())?;

        track_write!(self.writer, "|")?;
        for (h, w) in table.headers.iter().zip(widthes.iter().cloned()) {
            let s = match h.align {
                Align::Left => format!(":{:-<width$}-|", "-", width = w),
                Align::Center => format!(":{:-^width$}:|", "-", width = w),
                Align::Right => format!("-{:->width$}:|", "-", width = w),
            };
            track_write!(self.writer, "{}", s)?;
        }
        track!(self.newline())?;

        for row in &table.rows {
            track_write!(self.writer, "|")?;
            for (h, (item, w)) in table
                .headers
                .iter()
                .zip(row.items.iter().zip(widthes.iter().cloned()))
            {
                let s = match h.align {
                    Align::Left => format!(" {:<width$} |", item, width = w),
                    Align::Center => format!(" {:^width$} |", item, width = w),
                    Align::Right => format!(" {:>width$} |", item, width = w),
                };
                track_write!(self.writer, "{}", s)?;
            }
            track!(self.newline())?;
        }

        Ok(())
    }
}

#[derive(Debug)]
pub struct ListWriter<W> {
    writer: W,
    level: usize,
    number: Option<usize>,
}
impl<W: Write> ListWriter<W> {
    pub fn new(writer: W) -> Self {
        Self {
            writer,
            level: 0,
            number: None,
        }
    }

    pub fn numbered(mut self) -> Self {
        self.number = Some(1);
        self
    }

    pub fn item(&mut self, s: &str) -> Result<()> {
        for _ in 0..self.level {
            track_write!(self.writer, "  ")?;
        }
        if let Some(n) = self.number.as_mut() {
            track_writeln!(self.writer, "{}. {}", n, s)?;
            *n += 1;
        } else {
            track_writeln!(self.writer, "- {}", s)?;
        }
        Ok(())
    }
}

#[derive(Debug)]
pub struct Table {
    headers: Vec<ColumnHeader>,
    rows: Vec<Row>,
}
impl Table {
    pub fn new<I>(headers: I) -> Self
    where
        I: Iterator<Item = ColumnHeader>,
    {
        let headers = headers.collect();
        Self {
            headers,
            rows: Vec::new(),
        }
    }

    pub fn row(&mut self) -> &mut Row {
        self.rows.push(Row::default());
        self.rows.last_mut().unwrap_or_else(|| unreachable!())
    }
}

#[derive(Debug)]
pub struct ColumnHeader {
    name: String,
    align: Align,
}
impl ColumnHeader {
    pub fn new(name: &str, align: Align) -> Self {
        Self {
            name: name.to_owned(),
            align,
        }
    }
}

#[derive(Debug, Default)]
pub struct Row {
    items: Vec<String>,
}
impl Row {
    pub fn item<T>(&mut self, item: T) -> &mut Self
    where
        T: ToString,
    {
        self.items.push(item.to_string());
        self
    }
}

#[derive(Debug)]
#[allow(dead_code)]
pub enum Align {
    Left,
    Center,
    Right,
}
