use crate::io::NodeGraphInteractionState;
use crate::rules::ConnectPlan;
use jellyflow_core::core::{EdgeId, Graph, PortId};
use jellyflow_core::interaction::NodeGraphConnectionMode;

use super::common::{
    ConnectionEndpoints, ConnectionOpBuilder, add_existing_ports_edge_op, connection_exists,
    edge_between, resolve_policy_checked_connection,
};

/// Plans connecting two ports.
///
/// This is a rules-driven decision point used by the UI interaction loop.
/// The returned ops are intended to be applied as part of a single transaction.
pub fn plan_connect_with_mode_and_policy(
    graph: &Graph,
    a: PortId,
    b: PortId,
    mode: NodeGraphConnectionMode,
    state: &NodeGraphInteractionState,
) -> ConnectPlan {
    match plan_resolved_connect(graph, a, b, mode, state) {
        Ok(planned) => planned.into_plan(),
        Err(plan) => plan,
    }
}

pub(in crate::rules::connection) struct ResolvedConnectPlan<'a> {
    endpoints: ConnectionEndpoints<'a>,
    plan: ConnectPlan,
}

impl<'a> ResolvedConnectPlan<'a> {
    pub(in crate::rules::connection) fn endpoints(&self) -> &ConnectionEndpoints<'a> {
        &self.endpoints
    }

    pub(in crate::rules::connection) fn into_plan(self) -> ConnectPlan {
        self.plan
    }
}

pub(in crate::rules::connection) fn plan_resolved_connect<'a>(
    graph: &'a Graph,
    a: PortId,
    b: PortId,
    mode: NodeGraphConnectionMode,
    state: &NodeGraphInteractionState,
) -> Result<ResolvedConnectPlan<'a>, ConnectPlan> {
    let endpoints = match resolve_policy_checked_connection(graph, a, b, mode, state) {
        Ok(endpoints) => endpoints,
        Err(plan) => return Err(plan),
    };

    if connection_exists(
        graph,
        endpoints.edge_kind,
        endpoints.from_id,
        endpoints.to_id,
        None,
    ) {
        return Ok(ResolvedConnectPlan {
            endpoints,
            plan: ConnectPlan::accept(),
        });
    }

    let mut ops = ConnectionOpBuilder::with_endpoint_capacity_disconnects(graph, &endpoints, None);

    let add_edge = match add_existing_ports_edge_op(
        graph,
        EdgeId::new(),
        edge_between(endpoints.edge_kind, endpoints.from_id, endpoints.to_id),
    ) {
        Ok(op) => op,
        Err(plan) => return Err(plan),
    };
    ops.push(add_edge);

    Ok(ResolvedConnectPlan {
        endpoints,
        plan: ConnectPlan::from_ops(ops.into_ops()),
    })
}

/// Plans connecting two ports with default interaction policy.
pub fn plan_connect_with_mode(
    graph: &Graph,
    a: PortId,
    b: PortId,
    mode: NodeGraphConnectionMode,
) -> ConnectPlan {
    plan_connect_with_mode_and_policy(graph, a, b, mode, &NodeGraphInteractionState::default())
}

/// Plans connecting two ports (strict mode).
pub fn plan_connect(graph: &Graph, a: PortId, b: PortId) -> ConnectPlan {
    plan_connect_with_mode(graph, a, b, NodeGraphConnectionMode::Strict)
}
