use std::collections::HashMap;

use num_bigint::BigInt;
use num_traits::{One, ToPrimitive, Zero};

/// Trait that runtime nodes use to interact with the host application.
///
/// This is intentionally UI-agnostic: the host decides what "components" mean.
pub trait ComponentContext {
    fn get_text(&self, name: &str) -> Option<String>;
    fn set_text(&mut self, name: &str, text: String) -> bool;

    fn get_checked(&self, name: &str) -> Option<bool>;
    fn set_checked(&mut self, name: &str, checked: bool) -> bool;

    fn get_value(&self, name: &str) -> Option<f32>;
    fn set_value(&mut self, name: &str, value: f32) -> bool;

    fn get_color(&self, name: &str) -> Option<[f32; 4]>;
    fn set_color(&mut self, name: &str, color: [f32; 4]) -> bool;

    fn set_combobox_items(&mut self, name: &str, items: Vec<String>) -> Result<(), String>;
    fn get_combobox_value(&self, name: &str) -> Option<String>;

    fn show_message(&mut self, msg: &str);
    fn log(&mut self, msg: &str);

    fn http_get(&mut self, url: &str, headers: &str) -> Result<(u16, String), String>;
    fn http_post(&mut self, url: &str, headers: &str, body: &str) -> Result<(u16, String), String>;
    fn http_put(&mut self, url: &str, headers: &str, body: &str) -> Result<(u16, String), String>;
    fn http_delete(&mut self, url: &str, headers: &str) -> Result<(u16, String), String>;

    fn parse_json(&self, json: &str, path: &str) -> Result<String, String>;
    fn build_json(&self, pairs: &[(&str, &str)]) -> Result<String, String>;

    fn execute_query(&mut self, query: &str, params: &str) -> Result<u64, String>;
    fn query_rows(&self, query: &str, params: &str) -> Result<String, String>;
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum RuntimeValue {
    String(String),
    Float(f32),
    Bool(bool),
    Color([f32; 4]),
    BigInt(BigInt),
    Array(Vec<RuntimeValue>),
    Map(HashMap<String, RuntimeValue>),
    Tuple(Vec<RuntimeValue>),
    None,
}

impl RuntimeValue {
    pub fn as_string(&self) -> String {
        match self {
            RuntimeValue::String(s) => s.clone(),
            RuntimeValue::Float(f) => f.to_string(),
            RuntimeValue::Bool(b) => b.to_string(),
            RuntimeValue::Color(c) => format!("[{}, {}, {}, {}]", c[0], c[1], c[2], c[3]),
            RuntimeValue::BigInt(n) => n.to_string(),
            RuntimeValue::Array(arr) => {
                let elements: Vec<String> = arr.iter().map(|v| v.as_string()).collect();
                format!("[{}]", elements.join(", "))
            }
            RuntimeValue::Map(map) => {
                let pairs: Vec<String> = map
                    .iter()
                    .map(|(k, v)| format!("{}: {}", k, v.as_string()))
                    .collect();
                format!("{{{}}}", pairs.join(", "))
            }
            RuntimeValue::Tuple(tuple) => {
                let elements: Vec<String> = tuple.iter().map(|v| v.as_string()).collect();
                format!("({})", elements.join(", "))
            }
            RuntimeValue::None => String::new(),
        }
    }

    pub fn as_float(&self) -> f32 {
        match self {
            RuntimeValue::Float(f) => *f,
            RuntimeValue::String(s) => s.parse().unwrap_or(0.0),
            RuntimeValue::Bool(b) => {
                if *b {
                    1.0
                } else {
                    0.0
                }
            }
            RuntimeValue::BigInt(n) => n.to_f32().unwrap_or(0.0),
            RuntimeValue::Array(arr) => arr.len() as f32,
            RuntimeValue::Map(map) => map.len() as f32,
            RuntimeValue::Tuple(tuple) => tuple.len() as f32,
            _ => 0.0,
        }
    }

    pub fn as_bool(&self) -> bool {
        match self {
            RuntimeValue::Bool(b) => *b,
            RuntimeValue::String(s) => !s.is_empty(),
            RuntimeValue::Float(f) => *f != 0.0,
            RuntimeValue::BigInt(n) => !n.is_zero(),
            RuntimeValue::Array(arr) => !arr.is_empty(),
            RuntimeValue::Map(map) => !map.is_empty(),
            RuntimeValue::Tuple(tuple) => !tuple.is_empty(),
            _ => false,
        }
    }

    pub fn as_color(&self) -> [f32; 4] {
        match self {
            RuntimeValue::Color(c) => *c,
            _ => [1.0, 1.0, 1.0, 1.0],
        }
    }

    pub fn as_bigint(&self) -> BigInt {
        match self {
            RuntimeValue::BigInt(n) => n.clone(),
            RuntimeValue::Float(f) => BigInt::from(*f as i64),
            RuntimeValue::String(s) => s.parse().unwrap_or_else(|_| BigInt::zero()),
            RuntimeValue::Bool(b) => {
                if *b {
                    BigInt::one()
                } else {
                    BigInt::zero()
                }
            }
            _ => BigInt::zero(),
        }
    }

    pub fn as_array(&self) -> Vec<RuntimeValue> {
        match self {
            RuntimeValue::Array(arr) => arr.clone(),
            _ => Vec::new(),
        }
    }

    pub fn as_array_ref(&self) -> Option<&Vec<RuntimeValue>> {
        match self {
            RuntimeValue::Array(arr) => Some(arr),
            _ => None,
        }
    }

    pub fn as_array_mut(&mut self) -> Option<&mut Vec<RuntimeValue>> {
        match self {
            RuntimeValue::Array(arr) => Some(arr),
            _ => None,
        }
    }

    pub fn as_map_ref(&self) -> Option<&HashMap<String, RuntimeValue>> {
        match self {
            RuntimeValue::Map(map) => Some(map),
            _ => None,
        }
    }

    pub fn as_map_mut(&mut self) -> Option<&mut HashMap<String, RuntimeValue>> {
        match self {
            RuntimeValue::Map(map) => Some(map),
            _ => None,
        }
    }
}

/// Host-side runtime state used by the interpreter (variable storage + temp pin values).
#[derive(Default)]
pub struct HostRuntimeState {
    variables: HashMap<String, RuntimeValue>,
    temp_values: HashMap<(usize, String), RuntimeValue>,
}

impl HostRuntimeState {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn get_variable(&self, name: &str) -> Option<RuntimeValue> {
        self.variables.get(name).cloned()
    }

    pub fn set_variable(&mut self, name: String, value: RuntimeValue) {
        self.variables.insert(name, value);
    }

    pub fn clear_temp_values(&mut self) {
        self.temp_values.clear();
    }

    pub fn store_temp_value(&mut self, node_id: usize, pin_name: &str, value: RuntimeValue) {
        self.temp_values
            .insert((node_id, pin_name.to_string()), value);
    }

    pub fn get_temp_value(&self, node_id: usize, pin_name: &str) -> Option<&RuntimeValue> {
        self.temp_values.get(&(node_id, pin_name.to_string()))
    }
}

/// Combined host interface required by runtime nodes + interpreter.
pub trait HostContext: ComponentContext {
    fn state(&self) -> &HostRuntimeState;
    fn state_mut(&mut self) -> &mut HostRuntimeState;

    fn get_variable(&self, name: &str) -> Option<RuntimeValue> {
        self.state().get_variable(name)
    }

    fn set_variable(&mut self, name: String, value: RuntimeValue) {
        self.state_mut().set_variable(name, value)
    }

    fn clear_temp_values(&mut self) {
        self.state_mut().clear_temp_values()
    }

    fn store_temp_value(&mut self, node_id: usize, pin_name: &str, value: RuntimeValue) {
        self.state_mut().store_temp_value(node_id, pin_name, value)
    }

    fn get_temp_value(&self, node_id: usize, pin_name: &str) -> Option<&RuntimeValue> {
        self.state().get_temp_value(node_id, pin_name)
    }

    // Optional runtime-UI features: no-op defaults.
    fn open_modal(&mut self, _form_name: &str) {}
    fn close_modal(&mut self, _form_name: &str) {}

    fn set_table_data_from_array(&mut self, _component: &str, _data: &RuntimeValue) {}
    fn set_table_data_from_json(&mut self, _component: &str, _json: &str) -> Result<(), String> {
        Err("Table data not supported by host".to_string())
    }
    fn clear_table_data(&mut self, _component: &str) {}
}
