use std::collections::HashMap;

use crate::signal::store::SignalStore;
use crate::signal::SignalValue;
use crate::trace::{SignalId, Trace};

/// CSV trace reader. First column is timestamp, remaining columns are signal values.
pub struct CsvTrace {
    signal_names: Vec<String>,
    scopes: Vec<String>,
    timestamps: Vec<u64>,
    store: SignalStore,
    name_to_id: HashMap<String, usize>,
    widths: HashMap<String, u32>,
}

impl CsvTrace {
    pub fn from_file(path: &str) -> Result<Self, String> {
        let mut rdr = csv::Reader::from_path(path).map_err(|e| e.to_string())?;

        let headers: Vec<String> = rdr
            .headers()
            .map_err(|e| e.to_string())?
            .iter()
            .map(|s| s.trim().to_string())
            .collect();

        if headers.is_empty() {
            return Err("CSV has no columns".into());
        }

        // First column is timestamp; rest are signals
        let signal_names: Vec<String> = headers[1..].to_vec();
        let mut name_to_id: HashMap<String, usize> = HashMap::new();
        for (i, name) in signal_names.iter().enumerate() {
            name_to_id.insert(name.clone(), i);
        }

        let mut timestamps = Vec::new();
        let mut store = SignalStore::new();
        let mut widths: HashMap<String, u32> = HashMap::new();

        for result in rdr.records() {
            let record = result.map_err(|e| e.to_string())?;
            let idx = timestamps.len();

            let ts: u64 = record
                .get(0)
                .unwrap_or("0")
                .trim()
                .parse()
                .unwrap_or(0);
            timestamps.push(ts);

            for (col, name) in signal_names.iter().enumerate() {
                let raw = record.get(col + 1).unwrap_or("").trim();
                let val = if let Ok(v) = raw.parse::<u64>() {
                    // Track max width
                    let bits = if v == 0 { 1 } else { 64 - v.leading_zeros() };
                    let w = widths.entry(name.clone()).or_insert(1);
                    if bits > *w {
                        *w = bits;
                    }
                    SignalValue::Scalar(v)
                } else if let Ok(f) = raw.parse::<f64>() {
                    SignalValue::Real(f)
                } else if raw.is_empty() || raw == "x" || raw == "X" {
                    SignalValue::Unknown
                } else {
                    SignalValue::Str(raw.to_string())
                };
                store.set(col, idx, val);
            }
        }

        Ok(CsvTrace {
            signal_names,
            scopes: Vec::new(),
            timestamps,
            store,
            name_to_id,
            widths,
        })
    }
}

impl Trace for CsvTrace {
    fn signal_names(&self) -> &[String] {
        &self.signal_names
    }

    fn scopes(&self) -> &[String] {
        &self.scopes
    }

    fn max_index(&self) -> usize {
        if self.timestamps.is_empty() {
            0
        } else {
            self.timestamps.len() - 1
        }
    }

    fn timestamp(&self, index: usize) -> u64 {
        self.timestamps.get(index).copied().unwrap_or(0)
    }

    fn signal_value(&self, name: &str, index: usize) -> SignalValue {
        if let Some(&id) = self.name_to_id.get(name) {
            self.store.get(id, index)
        } else {
            SignalValue::Unknown
        }
    }

    fn signal_width(&self, name: &str) -> u32 {
        self.widths.get(name).copied().unwrap_or(1)
    }

    fn signal_id(&self, name: &str) -> Option<SignalId> {
        self.name_to_id.get(name).copied()
    }

    fn signal_values_range(&self, name: &str, start: usize, end: usize) -> Vec<SignalValue> {
        if let Some(&id) = self.name_to_id.get(name) {
            self.store.get_range(id, start, end)
        } else {
            vec![SignalValue::Unknown; end - start]
        }
    }

    fn signal_value_by_id(&self, id: SignalId, index: usize) -> SignalValue {
        self.store.get(id, index)
    }
}
