use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read};

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

/// Metadata for a signal parsed from VCD header.
#[derive(Clone, Debug)]
struct VcdSignal {
    full_name: String,
    width: u32,
    store_id: usize,
}

/// A parsed VCD trace.
pub struct VcdTrace {
    signal_names: Vec<String>,
    scopes: Vec<String>,
    name_to_signal: HashMap<String, VcdSignal>,
    vcd_id_to_store_id: HashMap<String, usize>,
    timestamps: Vec<u64>,
    store: SignalStore,
    next_store_id: usize,
}

impl VcdTrace {
    pub fn parse<R: Read>(reader: R) -> Result<Self, String> {
        let mut trace = VcdTrace {
            signal_names: Vec::new(),
            scopes: Vec::new(),
            name_to_signal: HashMap::new(),
            vcd_id_to_store_id: HashMap::new(),
            timestamps: Vec::new(),
            store: SignalStore::new(),
            next_store_id: 0,
        };

        let buf = BufReader::new(reader);
        let mut scope_stack: Vec<String> = Vec::new();
        let mut in_dumpvars = false;
        let mut current_index: Option<usize> = None;
        let mut scope_set: Vec<String> = Vec::new();

        // Stream line-by-line — no .collect(), constant memory overhead
        for line_result in buf.lines() {
            let line_owned = line_result.unwrap_or_default();
            let line = line_owned.trim();

            if line.is_empty() {
                continue;
            }

            // ── Header parsing ──

            if line.starts_with("$scope") {
                let parts: Vec<&str> = line.split_whitespace().collect();
                if parts.len() >= 3 {
                    scope_stack.push(parts[2].to_string());
                    let full_scope = scope_stack.join(".");
                    if !scope_set.contains(&full_scope) {
                        scope_set.push(full_scope);
                    }
                }
                continue;
            }

            if line.starts_with("$upscope") {
                scope_stack.pop();
                continue;
            }

            if line.starts_with("$var") {
                let parts: Vec<&str> = line.split_whitespace().collect();
                if parts.len() >= 5 {
                    let width: u32 = parts[2].parse().unwrap_or(1);
                    let vcd_id = parts[3].to_string();
                    let mut name = parts[4].to_string();
                    if let Some(idx) = name.find('[') {
                        name.truncate(idx);
                    }

                    let full_name = if scope_stack.is_empty() {
                        name.clone()
                    } else {
                        format!("{}.{}", scope_stack.join("."), name)
                    };

                    let store_id = if let Some(&sid) = trace.vcd_id_to_store_id.get(&vcd_id) {
                        sid
                    } else {
                        let sid = trace.next_store_id;
                        trace.next_store_id += 1;
                        trace.vcd_id_to_store_id.insert(vcd_id.clone(), sid);
                        sid
                    };

                    if !trace.signal_names.contains(&full_name) {
                        trace.signal_names.push(full_name.clone());
                    }

                    trace.name_to_signal.insert(
                        full_name,
                        VcdSignal {
                            full_name: name,
                            width,
                            store_id,
                        },
                    );
                }
                continue;
            }

            if line.starts_with("$enddefinitions")
                || line.starts_with("$timescale")
                || line.starts_with("$date")
                || line.starts_with("$version")
                || line.starts_with("$comment")
            {
                // These are self-contained or multi-line; skip content until $end
                // If $end is on the same line, we're done
                if !line.contains("$end") || line.starts_with("$end") {
                    // handled below by the $end check
                }
                continue;
            }

            if line.starts_with("$dumpvars") {
                in_dumpvars = true;
                continue;
            }

            if line == "$end" {
                in_dumpvars = false;
                continue;
            }

            // ── Value change section ──

            if line.starts_with('#') {
                let ts: u64 = line[1..].trim().parse().unwrap_or(0);
                trace.timestamps.push(ts);
                current_index = Some(trace.timestamps.len() - 1);

                // Forward-fill previous values
                if let Some(idx) = current_index {
                    if idx > 0 {
                        trace.store.forward_fill(idx);
                    }
                }
                continue;
            }

            if let Some(idx) = current_index {
                if line.starts_with('b') || line.starts_with('B') {
                    let parts: Vec<&str> = line.splitn(2, ' ').collect();
                    if parts.len() == 2 {
                        let val = parse_binary_str(parts[0]);
                        let vcd_id = parts[1].trim();
                        if let Some(&store_id) = trace.vcd_id_to_store_id.get(vcd_id) {
                            trace.store.set(store_id, idx, val);
                        }
                    }
                } else if line.starts_with('r') || line.starts_with('R') {
                    let parts: Vec<&str> = line.splitn(2, ' ').collect();
                    if parts.len() == 2 {
                        let f: f64 = parts[0][1..].parse().unwrap_or(0.0);
                        let vcd_id = parts[1].trim();
                        if let Some(&store_id) = trace.vcd_id_to_store_id.get(vcd_id) {
                            trace.store.set(store_id, idx, SignalValue::Real(f));
                        }
                    }
                } else if line.starts_with('0') || line.starts_with('1') {
                    let val = if line.starts_with('1') { 1u64 } else { 0u64 };
                    let vcd_id = &line[1..];
                    if let Some(&store_id) = trace.vcd_id_to_store_id.get(vcd_id) {
                        trace.store.set(store_id, idx, SignalValue::Scalar(val));
                    }
                } else if line.starts_with('x') || line.starts_with('X') {
                    let vcd_id = &line[1..];
                    if let Some(&store_id) = trace.vcd_id_to_store_id.get(vcd_id) {
                        trace.store.set(store_id, idx, SignalValue::Unknown);
                    }
                } else if line.starts_with('z') || line.starts_with('Z') {
                    let vcd_id = &line[1..];
                    if let Some(&store_id) = trace.vcd_id_to_store_id.get(vcd_id) {
                        trace.store.set(
                            store_id,
                            idx,
                            SignalValue::FourState(vec![crate::signal::Logic::Z]),
                        );
                    }
                }
            }
        }

        trace.scopes = scope_set;
        Ok(trace)
    }

    pub fn from_file(path: &str) -> Result<Self, String> {
        let file =
            std::fs::File::open(path).map_err(|e| format!("Failed to open {}: {}", path, e))?;
        Self::parse(file)
    }
}

impl Trace for VcdTrace {
    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(sig) = self.name_to_signal.get(name) {
            self.store.get(sig.store_id, index)
        } else {
            SignalValue::Unknown
        }
    }

    fn signal_width(&self, name: &str) -> u32 {
        self.name_to_signal.get(name).map(|s| s.width).unwrap_or(0)
    }

    fn signal_id(&self, name: &str) -> Option<SignalId> {
        self.name_to_signal.get(name).map(|s| s.store_id)
    }

    fn signal_values_range(&self, name: &str, start: usize, end: usize) -> Vec<SignalValue> {
        if let Some(sig) = self.name_to_signal.get(name) {
            self.store.get_range(sig.store_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)
    }
}
