//! Core types for the Forth VM: values, execution context, and shared state.

use arc_swap::ArcSwap;
use parking_lot::Mutex;
use rand::rngs::StdRng;
use std::collections::HashMap;
use std::sync::Arc;

use super::ops::Op;

/// Trait for accessing MIDI CC values. Implement this to provide CC memory to the Forth VM.
pub trait CcAccess: Send + Sync {
    /// Get the CC value for a given device, channel (0-15), and CC number (0-127).
    fn get_cc(&self, device: usize, channel: usize, cc: usize) -> u8;
}

/// Byte range in source text.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SourceSpan {
    pub start: u32,
    pub end: u32,
}

/// Concrete value resolved from a nondeterministic op, used for trace annotations.
#[derive(Clone, Debug)]
pub enum ResolvedValue {
    Int(i64),
    Float(f64),
    Bool(bool),
    Str(Arc<str>),
}

impl ResolvedValue {
    pub fn display(&self) -> String {
        match self {
            ResolvedValue::Int(i) => i.to_string(),
            ResolvedValue::Float(f) => format!("{f:.2}"),
            ResolvedValue::Bool(b) => if *b { "yes" } else { "no" }.into(),
            ResolvedValue::Str(s) => s.to_string(),
        }
    }
}

/// Spans and resolved values collected during a single evaluation, used for UI highlighting.
#[derive(Clone, Debug, Default)]
pub struct ExecutionTrace {
    pub executed_spans: Vec<SourceSpan>,
    pub selected_spans: Vec<SourceSpan>,
    pub resolved: Vec<(SourceSpan, ResolvedValue)>,
}

/// Per-step sequencer state passed into the VM.
pub struct StepContext<'a> {
    pub step: usize,
    pub beat: f64,
    pub bank: usize,
    pub pattern: usize,
    pub tempo: f64,
    pub phase: f64,
    pub slot: usize,
    pub runs: usize,
    pub iter: usize,
    pub speed: f64,
    pub fill: bool,
    pub nudge_secs: f64,
    pub cc_access: Option<&'a dyn CcAccess>,
    pub speed_key: &'a str,
    pub mouse_x: f64,
    pub mouse_y: f64,
    pub mouse_down: f64,
}

impl StepContext<'_> {
    pub fn step_duration(&self) -> f64 {
        60.0 / self.tempo / 4.0 / self.speed
    }
}

/// Underlying map for user-defined variables.
pub type VariablesMap = HashMap<String, Value>;
/// Shared variable store, swapped atomically after each step.
pub type Variables = Arc<ArcSwap<VariablesMap>>;
/// Shared user-defined word dictionary.
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
/// Shared random number generator.
pub type Rng = Arc<Mutex<StdRng>>;
pub type Stack = Mutex<Vec<Value>>;
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]);

/// Stack value in the Forth VM.
#[derive(Clone, Debug)]
pub enum Value {
    Int(i64, Option<SourceSpan>),
    Float(f64, Option<SourceSpan>),
    Str(Arc<str>, Option<SourceSpan>),
    Quotation(Arc<[Op]>, Option<SourceSpan>),
    CycleList(Arc<[Value]>),
    ArpList(Arc<[Value]>),
}

impl PartialEq for Value {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Value::Int(a, _), Value::Int(b, _)) => a == b,
            (Value::Float(a, _), Value::Float(b, _)) => a == b,
            (Value::Str(a, _), Value::Str(b, _)) => a == b,
            (Value::Quotation(a, _), Value::Quotation(b, _)) => a == b,
            (Value::CycleList(a), Value::CycleList(b)) => a == b,
            (Value::ArpList(a), Value::ArpList(b)) => a == b,
            _ => false,
        }
    }
}

impl Value {
    pub fn as_float(&self) -> Result<f64, String> {
        match self {
            Value::Float(f, _) => Ok(*f),
            Value::Int(i, _) => Ok(*i as f64),
            _ => Err("expected number".into()),
        }
    }

    pub(super) fn as_int(&self) -> Result<i64, String> {
        match self {
            Value::Int(i, _) => Ok(*i),
            Value::Float(f, _) => Ok(*f as i64),
            _ => Err("expected number".into()),
        }
    }

    pub(super) fn as_str(&self) -> Result<&str, String> {
        match self {
            Value::Str(s, _) => Ok(s),
            _ => Err("expected string".into()),
        }
    }

    pub(super) fn is_truthy(&self) -> bool {
        match self {
            Value::Int(i, _) => *i != 0,
            Value::Float(f, _) => *f != 0.0,
            Value::Str(s, _) => !s.is_empty(),
            Value::Quotation(..) => true,
            Value::CycleList(items) | Value::ArpList(items) => !items.is_empty(),
        }
    }

    pub(super) fn to_param_string(&self) -> String {
        match self {
            Value::Int(i, _) => i.to_string(),
            Value::Float(f, _) => f.to_string(),
            Value::Str(s, _) => s.to_string(),
            Value::Quotation(..) => String::new(),
            Value::CycleList(_) | Value::ArpList(_) => String::new(),
        }
    }

    pub(super) fn span(&self) -> Option<SourceSpan> {
        match self {
            Value::Int(_, s) | Value::Float(_, s) | Value::Str(_, s) | Value::Quotation(_, s) => *s,
            Value::CycleList(_) | Value::ArpList(_) => None,
        }
    }
}

#[derive(Clone, Debug, Default)]
pub(super) struct CmdRegister {
    sound: Option<Value>,
    params: Vec<(&'static str, Value)>,
    deltas: Vec<Value>,
    global_params: Vec<(&'static str, Value)>,
}

impl CmdRegister {
    pub(super) fn new() -> Self {
        Self {
            sound: None,
            params: Vec::with_capacity(16),
            deltas: Vec::with_capacity(4),
            global_params: Vec::new(),
        }
    }

    pub(super) fn set_sound(&mut self, val: Value) {
        self.sound = Some(val);
    }

    pub(super) fn set_param(&mut self, key: &'static str, val: Value) {
        self.params.push((key, val));
    }

    pub(super) fn set_deltas(&mut self, deltas: Vec<Value>) {
        self.deltas = deltas;
    }

    pub(super) fn deltas(&self) -> &[Value] {
        &self.deltas
    }

    pub(super) fn sound(&self) -> Option<&Value> {
        self.sound.as_ref()
    }

    pub(super) fn params(&self) -> &[(&'static str, Value)] {
        &self.params
    }

    pub(super) fn snapshot(&self) -> Option<CmdSnapshot<'_>> {
        if self.sound.is_some() || !self.params.is_empty() {
            Some((self.sound.as_ref(), self.params.as_slice()))
        } else {
            None
        }
    }

    pub(super) fn global_params(&self) -> &[(&'static str, Value)] {
        &self.global_params
    }

    pub(super) fn commit_global(&mut self) {
        self.global_params.append(&mut self.params);
        self.sound = None;
        self.deltas.clear();
    }

    pub(super) fn clear_global(&mut self) {
        self.global_params.clear();
    }

    pub fn set_global(&mut self, params: Vec<(&'static str, Value)>) {
        self.global_params = params;
    }

    pub fn take_global(&mut self) -> Vec<(&'static str, Value)> {
        std::mem::take(&mut self.global_params)
    }

    pub(super) fn clear(&mut self) {
        self.sound = None;
        self.params.clear();
        self.deltas.clear();
    }
}
