use super::FeedListItemID;
use super::item::FeedListItem;
use eyre::{Result, eyre};
use news_flash::models::{Category, CategoryID, CategoryMapping, Feed, FeedMapping, NEWSFLASH_TOPLEVEL};
use std::fmt::Display;

#[derive(Clone, Debug, Default)]
pub struct FeedListTree {
    pub top_level: Vec<FeedListItem>,
}

impl Display for FeedListTree {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        fn print_item_recursive(
            f: &mut std::fmt::Formatter<'_>,
            category: &[FeedListItem],
            level: &mut i32,
        ) -> std::fmt::Result {
            let mut new_level = *level + 1;
            for item in category {
                for _ in 0..new_level {
                    write!(f, "-- ")?;
                }

                writeln!(f, "{item}")?;
                print_item_recursive(f, &item.children, &mut new_level)?
            }
            Ok(())
        }

        print_item_recursive(f, &self.top_level, &mut 0)
    }
}

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

    pub fn add_feed(&mut self, feed: &Feed, mapping: &FeedMapping, item_count: u32) -> Result<()> {
        fn contains_feed(container: &[FeedListItem], feed: &Feed, mapping: &FeedMapping) -> bool {
            container.iter().any(|item| {
                if let FeedListItemID::Feed(item_mapping) = &item.id {
                    item_mapping.feed_id == feed.feed_id && item_mapping == mapping
                } else {
                    false
                }
            })
        }

        let container = if mapping.category_id == *NEWSFLASH_TOPLEVEL {
            &mut self.top_level
        } else if let Some(item) = self.find_category(&mapping.category_id) {
            &mut item.children
        } else {
            return Err(eyre!("tree does not contain parent ({mapping:?})"));
        };

        if contains_feed(container, feed, mapping) {
            return Err(eyre!("tree already contains feed '{}'", feed.feed_id));
        }

        let item = FeedListItem::from_feed(feed, mapping, item_count);
        container.push(item);
        Ok(())
    }

    pub fn add_categories(&mut self, mut pending_categories: Vec<(&Category, &CategoryMapping, u32)>) {
        fn contains_category(container: &[FeedListItem], category_id: &CategoryID) -> bool {
            container.iter().any(|item| item.id == *category_id)
        }
        fn add_categories_impl(
            parent_id: &CategoryID,
            container: &mut Vec<FeedListItem>,
            pending_categories: &mut Vec<(&Category, &CategoryMapping, u32)>,
        ) {
            // insert all categories that belong directly into this container
            pending_categories.retain(|&(category, mapping, item_count)| {
                let insert = &mapping.parent_id == parent_id && !contains_category(container, &category.category_id);
                if insert {
                    let item = FeedListItem::from_category(category, mapping, item_count);
                    container.push(item);
                }
                !insert
            });

            // go trough all child categories in this container and insert child categories into them
            for item in container {
                if let FeedListItemID::Category(category_id) = &item.id {
                    add_categories_impl(category_id, &mut item.children, pending_categories);
                }
            }
        }

        add_categories_impl(&NEWSFLASH_TOPLEVEL, &mut self.top_level, &mut pending_categories)
    }

    pub fn sort(&mut self) {
        fn sort_recurse(container: &mut Vec<FeedListItem>) {
            container.sort();

            for item in container {
                if item.id.is_feed() {
                    continue;
                }

                sort_recurse(&mut item.children);
            }
        }

        sort_recurse(&mut self.top_level)
    }

    fn find_category(&mut self, id: &CategoryID) -> Option<&mut FeedListItem> {
        fn find_category_recursive<'a>(
            id: &CategoryID,
            children: &'a mut Vec<FeedListItem>,
        ) -> Option<&'a mut FeedListItem> {
            for item in children {
                if item.id.is_feed() {
                    continue;
                }

                if item.id == *id {
                    return Some(item);
                }

                if item.children.is_empty() {
                    continue;
                }

                if let Some(category_model) = find_category_recursive(id, &mut item.children) {
                    return Some(category_model);
                }
            }
            None
        }

        find_category_recursive(id, &mut self.top_level)
    }
}
