use serde::{Serialize, Deserialize};
use std::fmt;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MTextParagraph {
    pub text: String,
    pub start_position: crate::geometry::Point,
    pub line_height: f64,
    pub indentation: f64,
    pub bullet: Option<MTextBullet>,
    pub formatting: super::TextFormatting,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MTextBullet {
    Dot,
    Circle,
    Square,
    Number(u32),
    Custom(String),
}

impl Default for MTextParagraph {
    fn default() -> Self {
        Self {
            text: String::new(),
            start_position: crate::geometry::Point::new(0.0, 0.0, 0.0),
            line_height: 1.0,
            indentation: 0.0,
            bullet: None,
            formatting: super::TextFormatting::default(),
        }
    }
}

impl MTextParagraph {
    pub fn new(text: String) -> Self {
        Self {
            text,
            ..Default::default()
        }
    }

    pub fn with_bullet(mut self, bullet: MTextBullet) -> Self {
        self.bullet = Some(bullet);
        self
    }

    pub fn with_indentation(mut self, indentation: f64) -> Self {
        self.indentation = indentation;
        self
    }

    pub fn with_formatting(mut self, formatting: super::TextFormatting) -> Self {
        self.formatting = formatting;
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MTextSymbol {
    Degree,
    PlusMinus,
    Diameter,
    Approximately,
    Ellipsis,
    Space,
    LineFeed,
    Custom(String),
}

impl MTextSymbol {
    pub fn to_string(&self) -> String {
        match self {
            MTextSymbol::Degree => "°".to_string(),
            MTextSymbol::PlusMinus => "±".to_string(),
            MTextSymbol::Diameter => "%%c".to_string(),
            MTextSymbol::Approximately => "≈".to_string(),
            MTextSymbol::Ellipsis => "...".to_string(),
            MTextSymbol::Space => " ".to_string(),
            MTextSymbol::LineFeed => "\n".to_string(),
            MTextSymbol::Custom(s) => s.clone(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MTextBlock {
    pub paragraphs: Vec<MTextParagraph>,
    pub base_position: crate::geometry::Point,
    pub width: Option<f64>,
    pub flow_direction: MTextFlowDirection,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MTextFlowDirection {
    LeftToRight,
    RightToLeft,
    TopToBottom,
}

impl Default for MTextBlock {
    fn default() -> Self {
        Self {
            paragraphs: Vec::new(),
            base_position: crate::geometry::Point::new(0.0, 0.0, 0.0),
            width: None,
            flow_direction: MTextFlowDirection::LeftToRight,
        }
    }
}

impl MTextBlock {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_paragraph(&mut self, paragraph: MTextParagraph) {
        self.paragraphs.push(paragraph);
    }

    pub fn add_plain_paragraph(&mut self, text: &str) {
        self.paragraphs.push(MTextParagraph::new(text.to_string()));
    }

    pub fn set_width(&mut self, width: f64) {
        self.width = Some(width);
    }

    pub fn set_flow_direction(&mut self, direction: MTextFlowDirection) {
        self.flow_direction = direction;
    }

    pub fn get_text(&self) -> String {
        self.paragraphs
            .iter()
            .map(|p| p.text.clone())
            .collect::<Vec<_>>()
            .join("\n")
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MTextColumn {
    pub column_number: u32,
    pub start_position: crate::geometry::Point,
    pub width: f64,
    pub height: f64,
    pub gutter: f64,
    pub contents: MTextBlock,
}

impl MTextColumn {
    pub fn new(column_number: u32, start_position: crate::geometry::Point, width: f64) -> Self {
        Self {
            column_number,
            start_position,
            width,
            height: 0.0,
            gutter: 0.5,
            contents: MTextBlock::default(),
        }
    }

    pub fn set_height(&mut self, height: f64) {
        self.height = height;
    }

    pub fn set_gutter(&mut self, gutter: f64) {
        self.gutter = gutter;
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MTextWithColumns {
    pub main_content: MTextBlock,
    pub columns: Vec<MTextColumn>,
    pub column_count: u32,
    pub auto_height: bool,
    pub column_flow: bool,
}

impl Default for MTextWithColumns {
    fn default() -> Self {
        Self {
            main_content: MTextBlock::default(),
            columns: Vec::new(),
            column_count: 1,
            auto_height: true,
            column_flow: false,
        }
    }
}

impl MTextWithColumns {
    pub fn new(column_count: u32) -> Self {
        Self {
            column_count,
            ..Default::default()
        }
    }

    pub fn create_columns(&mut self, start_position: crate::geometry::Point, total_width: f64, gutter: f64) {
        self.columns.clear();
        let column_width = (total_width - gutter * (self.column_count as f64 - 1.0)) / self.column_count as f64;

        for i in 0..self.column_count {
            let x = start_position.x + (column_width + gutter) * i as f64;
            let position = crate::geometry::Point::new(x, start_position.y, 0.0);
            self.columns.push(MTextColumn::new(i + 1, position, column_width));
        }
    }

    pub fn flow_content_to_columns(&mut self) {
        if !self.column_flow || self.columns.is_empty() {
            return;
        }

        let total_capacity: usize = self.columns.iter().map(|c| c.height as usize).sum();
        let mut current_column = 0;
        let mut current_height = 0.0;

        for paragraph in &mut self.main_content.paragraphs {
            if current_height + paragraph.line_height > self.columns[current_column as usize].height
                && current_column < self.column_count - 1
            {
                current_column += 1;
                current_height = 0.0;
            }

            self.columns[current_column as usize]
                .contents
                .add_paragraph(paragraph.clone());
            current_height += paragraph.line_height;
        }
    }
}

impl fmt::Display for MTextParagraph {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Paragraph(text=\"{}\", line_height={})",
            self.text, self.line_height
        )
    }
}

impl fmt::Display for MTextBlock {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "MTextBlock(paragraphs={}, width={})",
            self.paragraphs.len(),
            self.width.unwrap_or(0.0)
        )
    }
}