use crate::ast::*;
use crate::opcodes::*;

pub struct Compiler<'a> {
    module: &'a ModuleSoA,
    buf: Vec<u8>,
}

impl<'a> Compiler<'a> {
    pub fn new(module: &'a ModuleSoA) -> Self {
        Self {
            module,
            buf: Vec::new(),
        }
    }

    pub fn compile(mut self, root_nodes: &[NodeId]) -> Vec<u8> {
        self.buf.extend_from_slice(&MAGIC_HEADER);

        self.compile_span(root_nodes);

        self.buf
    }

    fn compile_span(&mut self, nodes: &[NodeId]) {
        for node in nodes {
            match node {
                NodeId::Element(id) => self.compile_element(*id),
                NodeId::Directive(id) => self.compile_directive(*id),
            }
        }
    }

    fn compile_block(&mut self, span: (u32, u32)) {
        let start = span.0 as usize;
        let len = span.1 as usize;
        let nodes = &self.module.hierarchy[start..start + len];
        self.compile_span(nodes);
        self.buf.push(OP_END_BLOCK);
    }

    fn compile_element(&mut self, id: u32) {
        let idx = id as usize;
        let typ = &self.module.elem_types[idx];

        self.buf.push(OP_ELEM_PUSH);
        self.write_string(typ);

        let (p_start, p_len) = self.module.elem_prop_spans[idx];
        for i in p_start..(p_start + p_len) {
            let key = &self.module.prop_keys[i as usize];
            let val = &self.module.prop_values[i as usize];
            self.compile_property(key, val);
        }

        if let Some(content) = &self.module.elem_content[idx] {
            self.buf.push(OP_CONTENT);
            self.compile_value(content);
        }

        let child_span = self.module.elem_child_spans[idx];
        if child_span.1 > 0 {
            self.compile_block(child_span);
        }

        self.buf.push(OP_ELEM_POP);
    }

    fn compile_directive(&mut self, id: u32) {
        let dir = &self.module.directives[id as usize];
        match dir {
            Directive::Version(v) => {
                self.buf.push(OP_VERSION);
                self.write_i64(*v);
            }
            Directive::Style(s) => {
                self.buf.push(OP_STYLE);
                self.write_string(s);
            }
            Directive::Global { name, value } => {
                self.buf.push(OP_GLOBAL);
                self.write_string(name);
                self.compile_value(value);
            }
            Directive::Singleton { name, prop_span } => {
                self.buf.push(OP_SINGLETON);
                self.write_string(name);
                self.write_u32(prop_span.1);

                let (start, len) = *prop_span;
                for i in start..(start + len) {
                    let key = &self.module.prop_keys[i as usize];
                    let val = &self.module.prop_values[i as usize];
                    self.write_string(key);
                    self.compile_value(val);
                }
            }
            Directive::Component {
                name,
                params,
                child_span,
            } => {
                self.buf.push(OP_COMPONENT);
                self.write_string(name);
                self.write_u32(params.len() as u32);
                for (pname, ptype) in params {
                    self.write_string(pname);
                    self.write_string(ptype);
                }
                self.compile_block(*child_span);
            }
            Directive::Let { name, value } => {
                self.buf.push(OP_LET);
                self.write_string(name);
                self.compile_value(value);
            }
            Directive::If {
                condition,
                child_span,
                else_span,
            } => {
                self.buf.push(OP_IF);
                self.compile_value(condition);
                self.compile_block(*child_span);
                if let Some(es) = else_span {
                    self.buf.push(1); // Has else
                    self.compile_block(*es);
                } else {
                    self.buf.push(0); // No else
                }
            }
            Directive::Each {
                item,
                collection,
                child_span,
            } => {
                self.buf.push(OP_EACH);
                self.write_string(item);
                self.compile_value(collection);
                self.compile_block(*child_span);
            }
            Directive::On {
                event,
                args,
                child_span,
            } => {
                self.buf.push(OP_ON);
                self.write_string(event);
                self.write_u32(args.len() as u32);
                for (k, v) in args {
                    self.write_string(k);
                    self.compile_value(v);
                }
                self.compile_block(*child_span);
            }
            Directive::RheiBlock(code) => {
                self.buf.push(OP_RHEI_BLK);
                self.write_string(code);
            }
        }
    }

    fn compile_property(&mut self, key: &str, val: &Value) {
        match val {
            Value::String(s) => {
                self.buf.push(OP_PROP_STR);
                self.write_string(key);
                self.write_string(s);
            }
            Value::Int(i) => {
                self.buf.push(OP_PROP_INT);
                self.write_string(key);
                self.write_i64(*i);
            }
            Value::Float(f) => {
                self.buf.push(OP_PROP_FLOAT);
                self.write_string(key);
                self.write_f64(*f);
            }
            Value::Bool(b) => {
                self.buf.push(OP_PROP_BOOL);
                self.write_string(key);
                self.buf.push(if *b { 1 } else { 0 });
            }
            Value::Color(c) => {
                self.buf.push(OP_PROP_COLOR);
                self.write_string(key);
                self.write_string(c);
            }
            Value::FsPath(p) => {
                self.buf.push(OP_PROP_FSPATH);
                self.write_string(key);
                self.write_string(p);
            }
            Value::Variable(v) => {
                self.buf.push(OP_PROP_VAR);
                self.write_string(key);
                self.write_string(v);
            }
            Value::Rhei(r) => {
                self.buf.push(OP_PROP_RHEI);
                self.write_string(key);
                self.write_string(r);
            }
        }
    }

    fn compile_value(&mut self, val: &Value) {
        match val {
            Value::String(s) => {
                self.buf.push(OP_PROP_STR);
                self.write_string(s);
            }
            Value::Int(i) => {
                self.buf.push(OP_PROP_INT);
                self.write_i64(*i);
            }
            Value::Float(f) => {
                self.buf.push(OP_PROP_FLOAT);
                self.write_f64(*f);
            }
            Value::Bool(b) => {
                self.buf.push(OP_PROP_BOOL);
                self.buf.push(if *b { 1 } else { 0 });
            }
            Value::Color(c) => {
                self.buf.push(OP_PROP_COLOR);
                self.write_string(c);
            }
            Value::FsPath(p) => {
                self.buf.push(OP_PROP_FSPATH);
                self.write_string(p);
            }
            Value::Variable(v) => {
                self.buf.push(OP_PROP_VAR);
                self.write_string(v);
            }
            Value::Rhei(r) => {
                self.buf.push(OP_PROP_RHEI);
                self.write_string(r);
            }
        }
    }

    fn write_string(&mut self, s: &str) {
        let bytes = s.as_bytes();
        self.write_u32(bytes.len() as u32);
        self.buf.extend_from_slice(bytes);
    }

    fn write_u32(&mut self, v: u32) {
        self.buf.extend_from_slice(&v.to_le_bytes());
    }

    fn write_i64(&mut self, v: i64) {
        self.buf.extend_from_slice(&v.to_le_bytes());
    }

    fn write_f64(&mut self, v: f64) {
        self.buf.extend_from_slice(&v.to_le_bytes());
    }
}
