use std::cell::RefCell;
use std::rc::Rc;

use crate::io::NodeGraphViewState;
use crate::runtime::conformance::{ConformanceCallbackTraceRecorder, ConformanceTraceEvent};
use crate::runtime::events::{NodeGraphGestureEvent, SubscriptionToken};
use crate::runtime::store::{DispatchError, DispatchOutcome, NodeGraphStore};
use crate::runtime::xyflow::callbacks::install_callbacks;
use jellyflow_core::core::{CanvasPoint, EdgeId, Graph, GroupId, NodeId};
use jellyflow_core::ops::GraphTransaction;

use super::super::fixtures::default_editor_config;

pub(in crate::runtime::tests) struct InteractionHarness {
    scenario: String,
    store: NodeGraphStore,
    events: Rc<RefCell<Vec<ConformanceTraceEvent>>>,
    _token: SubscriptionToken,
}

impl InteractionHarness {
    pub(in crate::runtime::tests) fn new(scenario: impl Into<String>, graph: Graph) -> Self {
        Self::with_view_state(scenario, graph, NodeGraphViewState::default())
    }

    pub(in crate::runtime::tests) fn with_view_state(
        scenario: impl Into<String>,
        graph: Graph,
        view_state: NodeGraphViewState,
    ) -> Self {
        let mut store = NodeGraphStore::new(graph, view_state, default_editor_config());
        let events = Rc::new(RefCell::new(Vec::new()));
        let event_trace = events.clone();
        let token = store.subscribe(move |event| {
            event_trace
                .borrow_mut()
                .push(ConformanceTraceEvent::from_store_event(event));
        });
        let gesture_trace = events.clone();
        store.subscribe_gesture_with_token(token, move |event| {
            gesture_trace
                .borrow_mut()
                .push(ConformanceTraceEvent::Gesture(event));
        });

        Self {
            scenario: scenario.into(),
            store,
            events,
            _token: token,
        }
    }

    pub(in crate::runtime::tests) fn store(&self) -> &NodeGraphStore {
        &self.store
    }

    pub(in crate::runtime::tests) fn store_mut(&mut self) -> &mut NodeGraphStore {
        &mut self.store
    }

    pub(in crate::runtime::tests) fn install_callback_trace(&mut self) -> SubscriptionToken {
        install_callbacks(
            &mut self.store,
            ConformanceCallbackTraceRecorder::new(self.events.clone()),
        )
    }

    pub(in crate::runtime::tests) fn dispatch_transaction(
        &mut self,
        tx: &GraphTransaction,
    ) -> Result<DispatchOutcome, DispatchError> {
        self.store.dispatch_transaction(tx)
    }

    pub(in crate::runtime::tests) fn set_viewport(&mut self, pan: CanvasPoint, zoom: f32) {
        self.store.set_viewport(pan, zoom);
    }

    pub(in crate::runtime::tests) fn set_selection(
        &mut self,
        nodes: Vec<NodeId>,
        edges: Vec<EdgeId>,
        groups: Vec<GroupId>,
    ) {
        self.store.set_selection(nodes, edges, groups);
    }

    pub(in crate::runtime::tests) fn emit_gesture(&mut self, event: NodeGraphGestureEvent) {
        self.store.emit_gesture(event);
    }

    pub(in crate::runtime::tests) fn assert_events(&self, expected: &[ConformanceTraceEvent]) {
        let actual = self.events.borrow();
        assert_eq!(
            actual.as_slice(),
            expected,
            "interaction harness trace mismatch for scenario `{}`\nexpected:\n{:#?}\nactual:\n{:#?}",
            self.scenario,
            expected,
            actual.as_slice(),
        );
    }
}
