use super::{Aggregate, Item, ViewKind};

#[derive(Debug, Clone, PartialEq)]
pub struct ViewDef {
    pub id: i64,
    pub name: String,
    pub kind: ViewKind,
    pub filter_value: String,
    pub sort_key: String,
    pub show_done: bool,
    pub filter_expr: String,
    pub columns: Vec<ViewColumn>,
    pub sections: Vec<ViewSection>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ViewColumn {
    pub field: String,
    pub category: Option<String>,
    pub heading: String,
    pub width: u16,
    pub aggregate: Aggregate,
    pub number_label: String,
    pub decimals: u8,
    pub decimal_separator: String,
    pub thousands_separator: String,
    pub negative_style: String,
    pub percent_total: bool,
    pub alignment: String,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ViewSection {
    pub id: i64,
    pub heading: String,
    pub filter_expr: String,
    pub collapsed: bool,
}

impl ViewDef {
    pub fn columns_spec(&self) -> String {
        self.columns
            .iter()
            .map(|column| {
                format!(
                    "{}{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
                    column.field,
                    column
                        .category
                        .as_ref()
                        .map(|category| format!("[{category}]"))
                        .unwrap_or_default(),
                    column.width,
                    column.heading,
                    column.aggregate,
                    column.number_label,
                    column.decimals,
                    match column.decimal_separator.as_str() {
                        "," => "comma",
                        "." => "dot",
                        other => other,
                    },
                    column.thousands_separator,
                    column.negative_style,
                    if column.percent_total {
                        "percent"
                    } else {
                        "value"
                    },
                    column.alignment,
                )
            })
            .collect::<Vec<_>>()
            .join(",")
    }

    pub fn sections_spec(&self) -> String {
        self.sections
            .iter()
            .map(|section| {
                format!(
                    "{}|{}{}",
                    section.heading,
                    section.filter_expr,
                    if section.collapsed { "|collapsed" } else { "" }
                )
            })
            .collect::<Vec<_>>()
            .join(";")
    }
}

impl ViewColumn {
    pub fn raw_value(&self, item: &Item) -> String {
        match self.field.as_str() {
            "item" => item.text.clone(),
            "categories" => item.category_names(),
            "when" => item.when_at.clone().unwrap_or_default(),
            "priority" => item.priority.to_string(),
            "note" => item.note.clone(),
            "numeric" => self
                .category
                .as_deref()
                .and_then(|category| item.numeric_value_for(category))
                .map(|value| value.to_string())
                .unwrap_or_default(),
            "value" => self
                .category
                .as_deref()
                .map_or(item.numeric_value, |category| {
                    item.numeric_value_for(category)
                })
                .map(|value| value.to_string())
                .unwrap_or_default(),
            "date" => self
                .category
                .as_deref()
                .and_then(|category| item.date_value_for(category))
                .unwrap_or_default()
                .into(),
            "done" => item.done_at.clone().unwrap_or_default(),
            "alarm" => item.alarm_at.clone().unwrap_or_default(),
            "recurrence" => item.recurrence.clone(),
            "created" => item.created_at.clone(),
            "updated" => item.updated_at.clone(),
            _ => String::new(),
        }
    }

    pub fn numeric_value(&self, item: &Item) -> Option<f64> {
        match self.field.as_str() {
            "numeric" => self
                .category
                .as_deref()
                .and_then(|category| item.numeric_value_for(category)),
            "value" => self
                .category
                .as_deref()
                .map_or(item.numeric_value, |category| {
                    item.numeric_value_for(category)
                }),
            "priority" => Some(item.priority as f64),
            _ => None,
        }
    }

    pub fn format_number(&self, value: f64) -> String {
        let negative = value.is_sign_negative();
        let raw = format!("{:.*}", self.decimals as usize, value.abs());
        let (integer, fraction) = raw.split_once('.').unwrap_or((&raw, ""));
        let separator = match self.thousands_separator.as_str() {
            "comma" => ",",
            "dot" => ".",
            "space" => " ",
            "none" | "" => "",
            other => other,
        };
        let mut grouped = String::new();
        for (index, character) in integer.chars().rev().enumerate() {
            if index > 0 && index % 3 == 0 {
                grouped.push_str(separator);
            }
            grouped.push(character);
        }
        let mut number = grouped.chars().rev().collect::<String>();
        if !fraction.is_empty() {
            number.push_str(&self.decimal_separator);
            number.push_str(fraction);
        }
        if !self.number_label.is_empty() {
            number = if matches!(self.number_label.as_str(), "$" | "€" | "£" | "¥") {
                format!("{}{number}", self.number_label)
            } else {
                format!("{number} {}", self.number_label)
            };
        }
        if negative {
            number = match self.negative_style.as_str() {
                "parentheses" => format!("({number})"),
                "trailing" => format!("{number}-"),
                _ => format!("-{number}"),
            };
        }
        number
    }

    pub fn aggregate_value<'a>(&self, items: impl Iterator<Item = &'a Item>) -> String {
        if self.aggregate == Aggregate::None {
            return String::new();
        }
        let values = items
            .filter_map(|item| self.numeric_value(item))
            .collect::<Vec<_>>();
        if self.aggregate == Aggregate::Count {
            return format!("Count {}", values.len());
        }
        if values.is_empty() {
            return String::new();
        }
        let value = match self.aggregate {
            Aggregate::Sum => values.iter().sum(),
            Aggregate::Average => values.iter().sum::<f64>() / values.len() as f64,
            Aggregate::Minimum => values.iter().copied().fold(f64::INFINITY, f64::min),
            Aggregate::Maximum => values.iter().copied().fold(f64::NEG_INFINITY, f64::max),
            Aggregate::None | Aggregate::Count => unreachable!(),
        };
        format!("{} {}", self.aggregate.label(), self.format_number(value))
    }
}
