/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/
use crate::recipes::RecipeGroup;
use hurrycurry_protocol::{Demand, DemandIndex, ItemIndex, Recipe, TileIndex, glam::IVec2};
use log::debug;
use std::collections::{BTreeMap, HashMap, HashSet};

pub fn filter_demands_and_recipes(
    initial_map: &HashMap<IVec2, (Vec<TileIndex>, Option<ItemIndex>)>,
    demands: &mut Vec<Demand>,
    recipes: &mut Vec<Recipe>,
    recipe_groups: &mut BTreeMap<String, RecipeGroup>,
) {
    debug!(
        "running demand filter with {} recipes and {} demands",
        recipes.len(),
        demands.len()
    );

    let map_tiles = initial_map
        .values()
        .flat_map(|(t, _)| t)
        .copied()
        .collect::<HashSet<_>>();
    let map_items = initial_map
        .values()
        .flat_map(|(_, i)| i)
        .copied()
        .collect::<HashSet<_>>();

    // Remove tile-bound recipes that cant be performed
    recipes.retain(|r| r.tile().is_none_or(|t| map_tiles.contains(&t)));

    let mut producable = HashMap::new();

    // Items already in the map have no cost
    producable.extend(map_items.iter().map(|i| (*i, 0.0)));

    loop {
        let prod_count = producable.len();

        for r in &*recipes {
            let output_count = r.outputs().filter(|o| !map_items.contains(o)).count();
            let Some(ingred_cost) = r
                .inputs()
                .map(|i| producable.get(&i).copied())
                .reduce(|a, b| {
                    if let (Some(a), Some(b)) = (a, b) {
                        Some(a + b)
                    } else {
                        None
                    }
                })
                .unwrap_or(Some(0.))
            else {
                continue;
            };

            let base_cost = match r {
                Recipe::Passive { speed, .. } => 1. + (1. / speed) * 0.15,
                Recipe::Active { speed, .. } => 0.5 + (1. / speed),
                Recipe::Instant { .. } => 0.5,
            };

            let output_cost = (ingred_cost + base_cost) / output_count as f32;
            for o in r.outputs() {
                let cost = producable.entry(o).or_insert(f32::INFINITY);
                *cost = cost.min(output_cost);
            }
        }

        if prod_count == producable.len() {
            break;
        }
    }

    recipes.retain(|r| {
        r.inputs().all(|o| producable.contains_key(&o))
            && r.outputs().all(|o| producable.contains_key(&o))
    });

    let mut demand_map = HashMap::new();
    let mut demands_out = Vec::new();
    for (oi, d) in demands.into_iter().enumerate() {
        if let Some(&cost) = producable.get(&d.input) {
            d.points = cost as i64 + 3;
            demand_map.insert(DemandIndex(oi), DemandIndex(demands_out.len()));
            demands_out.push(d.clone());
        }
    }
    *demands = demands_out;
    for rg in recipe_groups.values_mut() {
        rg.demands = rg
            .demands
            .clone()
            .into_iter()
            .filter_map(|i| demand_map.get(&i).copied())
            .collect();
        rg.repr = rg
            .repr
            .clone()
            .into_iter()
            .filter_map(|i| demand_map.get(&i).copied())
            .collect();
    }

    debug!(
        "{} applicable recipes and {} demands selected",
        recipes.len(),
        demands.len()
    )
}
