// Copyright 2025 Neuraville Inc.
// SPDX-License-Identifier: Apache-2.0

/*!
Vector offset morphology implementation.

Creates synapses based on vector offsets from source neurons.
*/

use crate::connectivity::rules::apply_vector_offset;
use crate::types::BduResult;
use feagi_npu_neural::types::{NeuronId, SynapticPsp, SynapticWeight};
use feagi_npu_neural::SynapseType;

/// Apply vector offset morphology directly on NPU with explicit dimensions
#[allow(clippy::too_many_arguments)]
pub fn apply_vectors_morphology_with_dimensions(
    npu: &mut feagi_npu_burst_engine::DynamicNPU,
    src_area_id: u32,
    dst_area_id: u32,
    vectors: Vec<(i32, i32, i32)>,
    dst_dimensions: (usize, usize, usize),
    weight: f32,
    psp: f32,
    synapse_attractivity: u8,
    synapse_type: SynapseType,
    delay_bursts: u8,
) -> BduResult<u32> {
    use crate::rng::get_rng;
    use rand::Rng;
    let mut rng = get_rng();

    if vectors.is_empty() {
        return Ok(0);
    }

    let src_neurons = npu.get_neurons_in_cortical_area(src_area_id);
    if src_neurons.is_empty() {
        return Ok(0);
    }

    let mut dst_pos_map = std::collections::HashMap::new();
    for dst_nid in npu.get_neurons_in_cortical_area(dst_area_id) {
        if let Some(coords) = npu.get_neuron_coordinates(dst_nid) {
            dst_pos_map.insert(coords, dst_nid);
        }
    }

    let mut synapse_count = 0u32;
    let mut seen_pairs: std::collections::HashSet<(u32, u32)> = std::collections::HashSet::new();

    for src_nid in src_neurons {
        let Some(src_pos) = npu.get_neuron_coordinates(src_nid) else {
            continue;
        };

        // Apply all vectors
        for &vector in &vectors {
            if let Some(dst_pos) = apply_vector_offset(src_pos, vector, 1.0, dst_dimensions) {
                // Note: Cannot collapse this if in Rust 2021 (let chains require Rust 2024)
                #[allow(clippy::collapsible_if)]
                if let Some(&dst_nid) = dst_pos_map.get(&dst_pos) {
                    // Within-call dedup: prevent the same morphology rule from producing
                    // duplicate synapses when multiple vectors collapse to the same target
                    // (e.g. after bounds clamping).
                    if !seen_pairs.insert((src_nid, dst_nid)) {
                        continue;
                    }
                    // NOTE: We intentionally do NOT skip creating a synapse when an
                    // outgoing synapse to the same target already exists. Separate
                    // morphology rules within a cortical mapping (e.g. a `projector`
                    // and a `block_to_block` both declared for the same src->dst pair)
                    // are parallel connections whose weights/PSPs are additive at
                    // propagation time. Dropping the second synapse loses the caller's
                    // configured post-synaptic current multiplier.
                    if rng.gen_range(0..100) < synapse_attractivity
                        && npu
                            .add_synapse(
                                NeuronId(src_nid),
                                NeuronId(dst_nid),
                                SynapticWeight(weight),
                                SynapticPsp(psp),
                                synapse_type,
                                0,
                                delay_bursts,
                            )
                            .is_ok()
                    {
                        synapse_count += 1;
                    }
                }
            }
        }
    }

    Ok(synapse_count)
}

/// Apply vector offset morphology directly on NPU
///
/// NOTE: This function calculates dimensions by scanning neurons (expensive).
/// For better performance, use `apply_vectors_morphology_with_dimensions` with
/// dimensions from ConnectomeManager.
#[allow(clippy::too_many_arguments)]
pub fn apply_vectors_morphology(
    npu: &mut feagi_npu_burst_engine::DynamicNPU,
    src_area_id: u32,
    dst_area_id: u32,
    vectors: Vec<(i32, i32, i32)>,
    weight: f32,
    psp: f32,
    synapse_attractivity: u8,
    synapse_type: SynapseType,
    delay_bursts: u8,
) -> BduResult<u32> {
    use crate::connectivity::core_morphologies::common::calculate_area_dimensions;
    let dst_dimensions = calculate_area_dimensions(npu, dst_area_id);
    apply_vectors_morphology_with_dimensions(
        npu,
        src_area_id,
        dst_area_id,
        vectors,
        dst_dimensions,
        weight,
        psp,
        synapse_attractivity,
        synapse_type,
        delay_bursts,
    )
}
