use std::collections::{HashMap, HashSet};

use loco_rs::prelude::*;
use sea_orm::entity::prelude::*;

pub use crate::models::_entities::categories::{ActiveModel, Column, Entity, Model};
pub type Categories = Entity;

#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        if !insert && self.updated_at.is_unchanged() {
            let mut this = self;
            this.updated_at = sea_orm::ActiveValue::Set(chrono::Utc::now().into());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

// implement your write-oriented logic here
impl ActiveModel {}

// implement your custom finders, selectors oriented logic here
impl Entity {}

// ---------------------------------------------------------------------------
// Queries
// ---------------------------------------------------------------------------

/// Every category, the source for tree building and validation.
pub async fn all(ctx: &AppContext) -> Result<Vec<Model>> {
    Ok(Entity::find().all(&ctx.db).await?)
}

/// Only published categories, for the storefront.
pub async fn published(ctx: &AppContext) -> Result<Vec<Model>> {
    Ok(Entity::find()
        .filter(Column::Published.eq(true))
        .all(&ctx.db)
        .await?)
}

// ---------------------------------------------------------------------------
// Hierarchy (adjacency list via `parent_id`)
// ---------------------------------------------------------------------------

/// Flatten the category forest into a depth-first ordered list of
/// `(category, depth)`, sorting siblings by position then name. `depth` is 0
/// for top-level categories and increases by one per level — templates use it
/// to indent.
pub fn tree(categories: &[Model]) -> Vec<(Model, usize)> {
    let mut children: HashMap<Option<i32>, Vec<&Model>> = HashMap::new();
    for category in categories {
        children.entry(category.parent_id).or_default().push(category);
    }
    for siblings in children.values_mut() {
        siblings.sort_by(|a, b| a.position.cmp(&b.position).then_with(|| a.name.cmp(&b.name)));
    }

    fn walk(
        parent: Option<i32>,
        depth: usize,
        children: &HashMap<Option<i32>, Vec<&Model>>,
        out: &mut Vec<(Model, usize)>,
    ) {
        if let Some(siblings) = children.get(&parent) {
            for category in siblings {
                out.push(((*category).clone(), depth));
                walk(Some(category.id), depth + 1, children, out);
            }
        }
    }

    let mut out = Vec::new();
    walk(None, 0, &children, &mut out);
    out
}

/// Ids of every descendant of `root` (children, grandchildren, …), not
/// including `root` itself.
pub fn descendant_ids(categories: &[Model], root: i32) -> HashSet<i32> {
    let mut set = HashSet::new();
    let mut stack = vec![root];
    while let Some(id) = stack.pop() {
        for child in categories.iter().filter(|c| c.parent_id == Some(id)) {
            if set.insert(child.id) {
                stack.push(child.id);
            }
        }
    }
    set
}

/// Ancestor chain (root first … immediate parent last) for breadcrumbs.
pub fn ancestors(categories: &[Model], start_parent: Option<i32>) -> Vec<Model> {
    let mut chain = Vec::new();
    let mut current = start_parent;
    while let Some(id) = current {
        match categories.iter().find(|c| c.id == id) {
            Some(category) => {
                current = category.parent_id;
                chain.push(category.clone());
            }
            None => break,
        }
    }
    chain.reverse();
    chain
}

/// Published direct children of `parent_id`, sorted for sub-navigation.
pub fn children_of(categories: &[Model], parent_id: i32) -> Vec<Model> {
    let mut children: Vec<Model> = categories
        .iter()
        .filter(|c| c.parent_id == Some(parent_id))
        .cloned()
        .collect();
    children.sort_by(|a, b| a.position.cmp(&b.position).then_with(|| a.name.cmp(&b.name)));
    children
}
