use std::collections::BTreeSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use super::import::{ImportAnchor, ImportConnector};
use super::transforms::compose_transform;
use super::{
    AnchorFrame, ConnectorKey, NodeKey, Scene, SourceCoordinateSystem, SourceUnits, Transform, Vec3,
};

mod error;
mod imports;
mod locks;
mod magnet;
mod metadata;
mod options;
mod parenting;
mod roll;
mod scale;
mod validation;
pub use error::ConnectionError;
pub use magnet::{ConnectionMagnetPreview, ConnectionMagnetVisualCue};
pub use metadata::{ConnectorMetadata, ConnectorPolarity, ConnectorRollPolicy};
pub use options::{
    ConnectOptions, ConnectionAlignment, ConnectionParenting, ConnectionRequest, ConnectionRoll,
};
use parenting::node_is_descendant_of;
use roll::roll_transform;
use scale::preserve_source_scale;
use validation::{
    inverse_transform, validate_connector_handedness, validate_connector_host_prepared,
    validate_connector_kinds, validate_connector_live, validate_connector_source_metadata,
    validate_connector_transform, validate_node_transform, validate_transform_scale,
};

#[derive(Debug, Clone)]
pub struct ConnectorFrame {
    node: NodeKey,
    local_transform: Transform,
    name: Option<String>,
    kind: Option<String>,
    allowed_mates: BTreeSet<String>,
    tags: BTreeSet<String>,
    snap_tolerance: Option<f32>,
    clearance_hint: Option<f32>,
    roll_policy: ConnectorRollPolicy,
    polarity: Option<ConnectorPolarity>,
    metadata: Option<ConnectorMetadata>,
    source_units: SourceUnits,
    source_coordinate_system: SourceCoordinateSystem,
    import_live: Option<Arc<AtomicBool>>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ConnectionPreview {
    source: ConnectorFrame,
    target: ConnectorFrame,
    resolved_transform: Transform,
    resolved_parent: Option<NodeKey>,
    connection_line: ConnectionLineOverlay,
    warnings: Vec<ConnectionWarning>,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ConnectionLineOverlay {
    source: NodeKey,
    target: NodeKey,
    start: Vec3,
    end: Vec3,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConnectionWarning {
    SourceMoved,
}

impl ConnectorFrame {
    pub fn new(node: NodeKey, local_transform: Transform) -> Self {
        Self {
            node,
            local_transform,
            name: None,
            kind: None,
            allowed_mates: BTreeSet::new(),
            tags: BTreeSet::new(),
            snap_tolerance: None,
            clearance_hint: None,
            roll_policy: ConnectorRollPolicy::Preserve,
            polarity: None,
            metadata: None,
            source_units: SourceUnits::Meters,
            source_coordinate_system: SourceCoordinateSystem::GltfYUpRightHanded,
            import_live: None,
        }
    }

    pub fn from_import_anchor(anchor: &ImportAnchor) -> Self {
        let mut connector = Self::new(anchor.placement_node(), anchor.connection_transform())
            .named(anchor.name())
            .with_source_metadata(anchor.source_units(), anchor.source_coordinate_system());
        connector.import_live = Some(anchor.live_flag());
        connector
    }

    pub fn from_import_connector(connector: &ImportConnector) -> Self {
        let mut frame = Self::new(connector.placement_node(), connector.connection_transform())
            .named(connector.name())
            .with_source_metadata(
                connector.source_units(),
                connector.source_coordinate_system(),
            );
        if let Some(kind) = connector.kind() {
            frame = frame.with_kind(kind);
        }
        for allowed_mate in connector.allowed_mates() {
            frame = frame.with_allowed_mate(allowed_mate);
        }
        for tag in connector.tags() {
            frame = frame.with_tag(tag);
        }
        if let Some(tolerance) = connector.snap_tolerance() {
            frame = frame.with_snap_tolerance(tolerance);
        }
        if let Some(clearance) = connector.clearance_hint() {
            frame = frame.with_clearance_hint(clearance);
        }
        frame = frame.with_roll_policy(connector.roll_policy());
        if let Some(polarity) = connector.polarity() {
            frame = frame.with_polarity(polarity);
        }
        if let Some(metadata) = connector.metadata() {
            frame = frame.with_metadata(metadata.clone());
        }
        frame.import_live = Some(connector.live_flag());
        frame
    }

    pub fn from_anchor_frame(anchor: &AnchorFrame) -> Self {
        let mut frame = Self::new(anchor.node(), anchor.local_transform())
            .with_source_metadata(anchor.source_units(), anchor.source_coordinate_system());
        if let Some(name) = anchor.name() {
            frame = frame.named(name);
        }
        frame.import_live = anchor.live_flag();
        frame
    }

    pub fn named(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    pub fn with_kind(mut self, kind: impl Into<String>) -> Self {
        self.kind = Some(kind.into());
        self
    }

    pub fn with_allowed_mate(mut self, kind: impl Into<String>) -> Self {
        self.allowed_mates.insert(kind.into());
        self
    }

    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
        self.tags.insert(tag.into());
        self
    }

    pub fn with_snap_tolerance(mut self, tolerance: f32) -> Self {
        self.snap_tolerance = Some(tolerance.max(0.0));
        self
    }

    pub fn with_clearance_hint(mut self, clearance: f32) -> Self {
        self.clearance_hint = Some(clearance.max(0.0));
        self
    }

    pub const fn with_roll_policy(mut self, policy: ConnectorRollPolicy) -> Self {
        self.roll_policy = policy;
        self
    }

    pub const fn with_polarity(mut self, polarity: ConnectorPolarity) -> Self {
        self.polarity = Some(polarity);
        self
    }

    pub fn with_metadata(mut self, metadata: ConnectorMetadata) -> Self {
        self.metadata = Some(metadata);
        self
    }

    fn with_source_metadata(
        mut self,
        units: SourceUnits,
        coordinate_system: SourceCoordinateSystem,
    ) -> Self {
        self.source_units = units;
        self.source_coordinate_system = coordinate_system;
        self
    }

    pub const fn node(&self) -> NodeKey {
        self.node
    }

    pub const fn local_transform(&self) -> Transform {
        self.local_transform
    }

    pub fn name(&self) -> Option<&str> {
        self.name.as_deref()
    }

    pub fn kind(&self) -> Option<&str> {
        self.kind.as_deref()
    }

    pub fn allowed_mates(&self) -> Vec<&str> {
        self.allowed_mates.iter().map(String::as_str).collect()
    }

    pub fn tags(&self) -> &BTreeSet<String> {
        &self.tags
    }

    pub const fn snap_tolerance(&self) -> Option<f32> {
        self.snap_tolerance
    }

    pub const fn clearance_hint(&self) -> Option<f32> {
        self.clearance_hint
    }

    pub const fn roll_policy(&self) -> ConnectorRollPolicy {
        self.roll_policy
    }

    pub const fn polarity(&self) -> Option<ConnectorPolarity> {
        self.polarity
    }

    pub const fn metadata(&self) -> Option<&ConnectorMetadata> {
        self.metadata.as_ref()
    }

    pub const fn source_units(&self) -> SourceUnits {
        self.source_units
    }

    pub const fn source_coordinate_system(&self) -> SourceCoordinateSystem {
        self.source_coordinate_system
    }

    fn is_live(&self) -> bool {
        match &self.import_live {
            Some(flag) => flag.load(Ordering::Acquire),
            None => true,
        }
    }
}

impl ConnectionPreview {
    pub const fn source(&self) -> &ConnectorFrame {
        &self.source
    }

    pub const fn target(&self) -> &ConnectorFrame {
        &self.target
    }

    pub const fn resolved_transform(&self) -> Transform {
        self.resolved_transform
    }

    pub const fn resolved_parent(&self) -> Option<NodeKey> {
        self.resolved_parent
    }

    pub fn warnings(&self) -> &[ConnectionWarning] {
        &self.warnings
    }

    pub const fn connection_line(&self) -> ConnectionLineOverlay {
        self.connection_line
    }
}

impl ConnectionLineOverlay {
    pub const fn source(self) -> NodeKey {
        self.source
    }

    pub const fn target(self) -> NodeKey {
        self.target
    }

    pub const fn start(self) -> Vec3 {
        self.start
    }

    pub const fn end(self) -> Vec3 {
        self.end
    }
}

impl Scene {
    pub fn add_connector(
        &mut self,
        connector: ConnectorFrame,
    ) -> Result<ConnectorKey, ConnectionError> {
        if !self.nodes.contains_key(connector.node) {
            return Err(ConnectionError::NodeNotFound(connector.node));
        }
        validate_connector_live(&connector, None)?;
        let key = self.connectors.insert(connector);
        self.structure_revision = self.structure_revision.saturating_add(1);
        Ok(key)
    }

    pub fn connector(&self, connector: ConnectorKey) -> Result<&ConnectorFrame, ConnectionError> {
        let frame = self
            .connectors
            .get(connector)
            .ok_or(ConnectionError::MissingConnector { connector })?;
        validate_connector_live(frame, Some(connector))?;
        if !self.nodes.contains_key(frame.node) {
            return Err(ConnectionError::NodeNotFound(frame.node));
        }
        Ok(frame)
    }

    pub fn connector_named(&self, name: &str) -> Result<ConnectorKey, ConnectionError> {
        let matches = self
            .connectors
            .iter()
            .filter(|(_, connector)| connector.name() == Some(name))
            .map(|(key, _)| key)
            .collect::<Vec<_>>();
        match matches.as_slice() {
            [] => Err(ConnectionError::MissingConnectorName {
                name: name.to_string(),
            }),
            [connector] => {
                self.connector(*connector)?;
                Ok(*connector)
            }
            _ => Err(ConnectionError::AmbiguousConnector {
                name: name.to_string(),
                matches,
            }),
        }
    }

    pub fn validate_connections(
        &self,
        requests: &[ConnectionRequest],
    ) -> Result<Vec<ConnectionPreview>, ConnectionError> {
        requests
            .iter()
            .map(|request| {
                let source = self.connector(request.source())?.clone();
                let target = self.connector(request.target())?.clone();
                self.preview_connection(source, target, request.options())
            })
            .collect()
    }

    pub fn connect_by_key(
        &mut self,
        source: ConnectorKey,
        target: ConnectorKey,
        options: ConnectOptions,
    ) -> Result<ConnectionPreview, ConnectionError> {
        let source_frame = self.connector(source)?.clone();
        let target_frame = self.connector(target)?.clone();
        self.connect(source_frame, target_frame, options)
    }

    pub fn preview_connection(
        &self,
        source: ConnectorFrame,
        target: ConnectorFrame,
        options: ConnectOptions,
    ) -> Result<ConnectionPreview, ConnectionError> {
        validate_connector_live(&source, None)?;
        validate_connector_live(&target, None)?;
        validate_connector_handedness(&source)?;
        validate_connector_handedness(&target)?;
        validate_connector_source_metadata(&source, &target)?;
        validate_connector_kinds(&source, &target)?;
        let source_node = self
            .nodes
            .get(source.node)
            .ok_or(ConnectionError::NodeNotFound(source.node))?;
        let target_node = self
            .nodes
            .get(target.node)
            .ok_or(ConnectionError::NodeNotFound(target.node))?;
        validate_connector_host_prepared(&source, &source_node.kind)?;
        validate_connector_host_prepared(&target, &target_node.kind)?;
        if self.connection_locked_nodes.contains(&source.node) {
            return Err(ConnectionError::ConnectionWouldMoveLockedNode { node: source.node });
        }

        validate_connector_transform(&source, options)?;
        validate_connector_transform(&target, options)?;
        validate_node_transform(source.node, source_node.transform, options)?;
        let target_world = self
            .world_transform(target.node)
            .ok_or(ConnectionError::NodeNotFound(target.node))?;
        let source_current_world = self
            .world_transform(source.node)
            .ok_or(ConnectionError::NodeNotFound(source.node))?;
        validate_transform_scale(target.node, target_world, options)?;
        let resolved_parent = match options.parenting() {
            ConnectionParenting::PreserveSourceParent => source_node.parent(),
            ConnectionParenting::ReparentSourceToTargetParent => target_node.parent(),
        };
        if let Some(parent) = resolved_parent {
            if !self.nodes.contains_key(parent) {
                return Err(ConnectionError::NodeNotFound(parent));
            }
            if node_is_descendant_of(self, parent, source.node) {
                return Err(ConnectionError::ConnectionWouldCreateCycle {
                    source: source.node,
                    parent,
                });
            }
        }
        let source_parent_world = resolved_parent
            .and_then(|parent| self.world_transform(parent))
            .unwrap_or(Transform::IDENTITY);
        if let Some(parent) = resolved_parent {
            validate_transform_scale(parent, source_parent_world, options)?;
        }

        let target_connector_world = compose_transform(target_world, target.local_transform);
        let target_offset = compose_transform(target_connector_world, options.mate_offset);
        let target_aligned = compose_transform(target_offset, options.alignment_transform());
        let source_current_connector =
            compose_transform(source_current_world, source.local_transform);
        let target_mated = compose_transform(
            target_aligned,
            roll_transform(options.roll(), source_current_connector, target_aligned),
        );
        let source_connector_inverse =
            inverse_transform(source.local_transform).ok_or_else(|| {
                ConnectionError::DegenerateConnectorFrame {
                    connector: source.name.clone(),
                }
            })?;
        let desired_source_world = preserve_source_scale(
            compose_transform(target_mated, source_connector_inverse),
            source_current_world.scale,
            source.local_transform,
            target_mated.translation,
        );
        let parent_inverse = inverse_transform(source_parent_world).ok_or_else(|| {
            ConnectionError::DegenerateConnectorFrame {
                connector: source.name.clone(),
            }
        })?;
        let resolved_transform = compose_transform(parent_inverse, desired_source_world);

        Ok(ConnectionPreview {
            connection_line: ConnectionLineOverlay {
                source: source.node,
                target: target.node,
                start: source_current_connector.translation,
                end: target_mated.translation,
            },
            source,
            target,
            resolved_transform,
            resolved_parent,
            warnings: vec![ConnectionWarning::SourceMoved],
        })
    }

    pub fn connect(
        &mut self,
        source: ConnectorFrame,
        target: ConnectorFrame,
        options: ConnectOptions,
    ) -> Result<ConnectionPreview, ConnectionError> {
        let preview = self.preview_connection(source, target, options)?;
        self.reparent_for_connection(preview.source.node, preview.resolved_parent)?;
        self.set_transform(preview.source.node, preview.resolved_transform)
            .map_err(|_| ConnectionError::NodeNotFound(preview.source.node))?;
        Ok(preview)
    }

    fn reparent_for_connection(
        &mut self,
        node: NodeKey,
        new_parent: Option<NodeKey>,
    ) -> Result<(), ConnectionError> {
        let current_parent = self
            .nodes
            .get(node)
            .ok_or(ConnectionError::NodeNotFound(node))?
            .parent();
        if current_parent == new_parent {
            return Ok(());
        }
        let Some(new_parent) = new_parent else {
            return Err(ConnectionError::NodeNotFound(node));
        };
        if !self.nodes.contains_key(new_parent) {
            return Err(ConnectionError::NodeNotFound(new_parent));
        }
        if node_is_descendant_of(self, new_parent, node) {
            return Err(ConnectionError::ConnectionWouldCreateCycle {
                source: node,
                parent: new_parent,
            });
        }
        if let Some(old_parent) = current_parent
            && let Some(parent_node) = self.nodes.get_mut(old_parent)
        {
            parent_node.children.retain(|child| *child != node);
        }
        self.nodes
            .get_mut(node)
            .ok_or(ConnectionError::NodeNotFound(node))?
            .parent = Some(new_parent);
        if !self.nodes[new_parent].children.contains(&node) {
            self.nodes[new_parent].children.push(node);
        }
        self.structure_revision = self.structure_revision.saturating_add(1);
        self.transform_revision = self.transform_revision.saturating_add(1);
        Ok(())
    }
}

impl PartialEq for ConnectorFrame {
    fn eq(&self, other: &Self) -> bool {
        self.node == other.node
            && self.local_transform == other.local_transform
            && self.name == other.name
            && self.kind == other.kind
            && self.allowed_mates == other.allowed_mates
            && self.tags == other.tags
            && self.snap_tolerance == other.snap_tolerance
            && self.clearance_hint == other.clearance_hint
            && self.roll_policy == other.roll_policy
            && self.polarity == other.polarity
            && self.metadata == other.metadata
            && self.source_units == other.source_units
            && self.source_coordinate_system == other.source_coordinate_system
    }
}
