//! Code generator — transforms el-ui AST into JavaScript module source.
//!
//! Each component becomes a class that:
//!   1. Extends `Component` from the el-ui runtime.
//!   2. Stores each `state` field as a node in an in-instance `Graph`.
//!   3. Implements `render()` returning an HTML string.
//!   4. Uses `setState()` to trigger spreading activation and DOM patching.
//!
//! ## Codegen targets
//!
//! The `CodegenTarget` enum selects the output format:
//!
//! - `Web` — ES2022 module (current, default behavior)
//! - `Server` — Rust code calling the `el-platform` server backend for SSR
//! - `Native(Platform)` — Rust code calling the `el-platform` native backend trait

use crate::ast::*;
use crate::error::CompileResult;

/// Which native platform to target when using `CodegenTarget::Native`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Platform {
    Ios,
    Android,
    Macos,
    Linux,
    Windows,
}

impl Platform {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Ios => "ios",
            Self::Android => "android",
            Self::Macos => "macos",
            Self::Linux => "linux",
            Self::Windows => "windows",
        }
    }
}

/// Selects the output format for the code generator.
///
/// - `Web` — ES2022 JavaScript module (default, uses the el-ui JS runtime)
/// - `Server` — Rust module that uses `el_platform::ServerBackend` for SSR.
///   The generated Rust struct implements a `render_to_html()` method.
/// - `Native(Platform)` — Rust module calling `el_platform::PlatformBackend`
///   for the specified native platform (iOS, Android, macOS, Linux, Windows).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CodegenTarget {
    /// Current default — ES2022 JavaScript module for the browser.
    Web,
    /// Server-side rendering — generates Rust code using `el_platform::ServerBackend`.
    Server,
    /// Native platform — generates Rust code using the platform backend trait.
    Native(Platform),
}

impl CodegenTarget {
    pub fn is_web(&self) -> bool {
        matches!(self, Self::Web)
    }

    pub fn is_rust_output(&self) -> bool {
        matches!(self, Self::Server | Self::Native(_))
    }
}

pub struct Codegen {
    runtime_path: String,
    /// The compilation target. Defaults to `Web`.
    pub target: CodegenTarget,
}

impl Codegen {
    pub fn new(runtime_path: &str) -> Self {
        Self {
            runtime_path: runtime_path.to_owned(),
            target: CodegenTarget::Web,
        }
    }

    pub fn with_target(mut self, target: CodegenTarget) -> Self {
        self.target = target;
        self
    }

    pub fn generate(&self, components: &[Component]) -> CompileResult<String> {
        match &self.target {
            CodegenTarget::Web => self.generate_web(components),
            CodegenTarget::Server => self.generate_server_rust(components),
            CodegenTarget::Native(platform) => self.generate_native_rust(components, platform),
        }
    }

    fn generate_web(&self, components: &[Component]) -> CompileResult<String> {
        let mut out = String::new();

        // Runtime import
        out.push_str(&format!(
            "import {{ Component, Graph, Renderer, Router, mount }} from '{}';\n\n",
            self.runtime_path
        ));

        for component in components {
            out.push_str(&self.gen_component(component)?);
            out.push('\n');
        }

        // Export all component names
        let names: Vec<&str> = components.iter().map(|c| c.name.as_str()).collect();
        if !names.is_empty() {
            out.push_str(&format!("export {{ {} }};\n", names.join(", ")));
        }

        Ok(out)
    }

    /// Generate Rust code for the `server` target.
    ///
    /// The output is a Rust module where each component is a struct implementing
    /// `render_to_html(&self) -> String` using `el_platform::ServerBackend`.
    fn generate_server_rust(&self, components: &[Component]) -> CompileResult<String> {
        let mut out = String::new();
        out.push_str("//! el-ui SSR — generated by el-ui-compiler (target: server)\n");
        out.push_str("//! Do not edit. Re-generate with: el-ui-compiler --target server\n\n");
        out.push_str("use el_platform::{ServerBackend, PlatformBackend, PlatformNode};\n\n");

        for comp in components {
            out.push_str(&format!("/// Server-side rendered component: {}\n", comp.name));
            out.push_str(&format!("pub struct {} {{\n", comp.name));
            for prop in &comp.props {
                out.push_str(&format!("    pub {}: String,\n", prop.name));
            }
            for state in &comp.state {
                out.push_str(&format!("    pub {}: String,\n", state.name));
            }
            out.push_str("}\n\n");

            out.push_str(&format!("impl {} {{\n", comp.name));
            out.push_str("    pub fn render_to_html(&self) -> String {\n");
            out.push_str("        let backend = ServerBackend::new();\n");
            // Generate a simple element tree based on the template
            out.push_str("        let root = self.build_node_tree();\n");
            out.push_str("        backend.render_to_string(&root).unwrap_or_default()\n");
            out.push_str("    }\n\n");
            out.push_str("    fn build_node_tree(&self) -> PlatformNode {\n");
            out.push_str("        // TODO: full template → PlatformNode tree codegen\n");
            out.push_str("        // The compiler translates each TemplateNode into\n");
            out.push_str("        // PlatformNode::element() / PlatformNode::text() calls.\n");
            out.push_str("        PlatformNode::element(\"div\")\n");
            out.push_str("    }\n");
            out.push_str("}\n\n");
        }

        Ok(out)
    }

    /// Generate Rust code for a `native` target.
    ///
    /// The output is a Rust module where each component builds a `PlatformNode`
    /// tree and calls the appropriate backend to mount it.
    fn generate_native_rust(&self, components: &[Component], platform: &Platform) -> CompileResult<String> {
        let backend_type = match platform {
            Platform::Ios => "IosBackend",
            Platform::Android => "AndroidBackend",
            Platform::Macos => "MacosBackend",
            Platform::Linux => "LinuxBackend",
            Platform::Windows => "WindowsBackend",
        };

        let mut out = String::new();
        out.push_str(&format!(
            "//! el-ui native — generated by el-ui-compiler (target: {})\n",
            platform.as_str()
        ));
        out.push_str("//! Do not edit. Re-generate with: el-ui-compiler --target <platform>\n\n");
        out.push_str(&format!(
            "use el_platform::{{{}  as Backend, PlatformBackend, PlatformNode}};\n\n",
            backend_type
        ));

        for comp in components {
            out.push_str(&format!("/// Native component: {} ({})\n", comp.name, platform.as_str()));
            out.push_str(&format!("pub struct {} {{\n", comp.name));
            for prop in &comp.props {
                out.push_str(&format!("    pub {}: String,\n", prop.name));
            }
            for state in &comp.state {
                out.push_str(&format!("    pub {}: String,\n", state.name));
            }
            out.push_str("    backend: Backend,\n");
            out.push_str("}\n\n");

            out.push_str(&format!("impl {} {{\n", comp.name));
            out.push_str("    pub fn new() -> Self {\n");
            out.push_str("        Self {\n");
            for prop in &comp.props {
                let default = prop.default.as_deref().unwrap_or("\"\"");
                out.push_str(&format!("            {}: {}.to_string(),\n", prop.name, default));
            }
            for state in &comp.state {
                out.push_str(&format!("            {}: {}.to_string(),\n", state.name, state.initial));
            }
            out.push_str("            backend: Backend::new(),\n");
            out.push_str("        }\n");
            out.push_str("    }\n\n");
            out.push_str("    pub fn mount(&self, container_id: &str) -> el_platform::PlatformResult<()> {\n");
            out.push_str("        let root = self.build_node_tree();\n");
            out.push_str("        self.backend.mount(root, container_id)\n");
            out.push_str("    }\n\n");
            out.push_str("    fn build_node_tree(&self) -> PlatformNode {\n");
            out.push_str("        // TODO: full template → PlatformNode tree codegen\n");
            out.push_str("        PlatformNode::element(\"div\")\n");
            out.push_str("    }\n");
            out.push_str("}\n\n");
        }

        Ok(out)
    }

    fn gen_component(&self, comp: &Component) -> CompileResult<String> {
        let mut out = String::new();

        out.push_str(&format!("class {} extends Component {{\n", comp.name));

        // constructor
        out.push_str("    constructor(props = {}) {\n");
        out.push_str("        super();\n");
        out.push_str("        this.props = props;\n");
        out.push_str("        this._graph = new Graph();\n");
        out.push_str("        this._stateNodes = {};\n");
        out.push_str("        this._state = {};\n");

        // Validate and set props
        if !comp.props.is_empty() {
            out.push_str("        // Props\n");
            for prop in &comp.props {
                let default_js = prop.default.as_deref()
                    .map(|d| translate_el_to_js(d))
                    .unwrap_or_else(|| "undefined".to_owned());
                out.push_str(&format!(
                    "        this._props_{name} = props.{name} !== undefined ? props.{name} : {default};\n",
                    name = prop.name,
                    default = default_js,
                ));
            }
        }

        // Seed state nodes
        if !comp.state.is_empty() {
            out.push_str("        // State nodes (Engram graph seeds)\n");
            for s in &comp.state {
                let initial_js = translate_el_to_js(&s.initial);
                out.push_str(&format!(
                    "        this._stateNodes['{name}'] = this._graph.seed({{ type: 'state', name: '{name}', content: {initial} }});\n",
                    name = s.name,
                    initial = initial_js,
                ));
                out.push_str(&format!(
                    "        this._state['{name}'] = {initial};\n",
                    name = s.name,
                    initial = initial_js,
                ));
            }
        }

        // Subscribe to state node changes for reactive re-render
        if !comp.state.is_empty() {
            out.push_str("        // Subscribe to graph activation events\n");
            out.push_str("        for (const [key, nodeId] of Object.entries(this._stateNodes)) {\n");
            out.push_str("            this._graph.subscribe(nodeId, (node) => {\n");
            out.push_str("                this._state[key] = node.content;\n");
            out.push_str("                if (this._renderer) this._renderer.patch();\n");
            out.push_str("            });\n");
            out.push_str("        }\n");
        }

        out.push_str("    }\n\n");

        // setState method
        out.push_str("    setState(name, value) {\n");
        out.push_str("        if (this._stateNodes[name] !== undefined) {\n");
        out.push_str("            this._graph.update(this._stateNodes[name], value);\n");
        out.push_str("        }\n");
        out.push_str("    }\n\n");

        // User-defined methods
        for method in &comp.methods {
            out.push_str(&self.gen_method(method, comp)?);
            out.push('\n');
        }

        // render()
        out.push_str("    render() {\n");
        out.push_str("        const __self = this;\n");

        // Expose state variables
        for s in &comp.state {
            out.push_str(&format!(
                "        const {name} = this._state['{name}'];\n",
                name = s.name,
            ));
        }
        // Expose props
        for p in &comp.props {
            out.push_str(&format!(
                "        const {name} = this._props_{name};\n",
                name = p.name,
            ));
        }

        out.push_str("        return `");
        let template_js = self.gen_template_nodes(&comp.template.nodes, comp)?;
        out.push_str(&template_js);
        out.push_str("`;\n");
        out.push_str("    }\n\n");

        out.push_str("}\n");
        Ok(out)
    }

    fn gen_method(&self, method: &Method, comp: &Component) -> CompileResult<String> {
        let mut out = String::new();
        let params: Vec<String> = method.params.iter()
            .map(|(n, _)| n.clone())
            .collect();

        out.push_str(&format!(
            "    {}({}) {{\n",
            method.name,
            params.join(", ")
        ));

        // Expose state in method body
        for s in &comp.state {
            out.push_str(&format!(
                "        const {name} = this._state['{name}'];\n",
                name = s.name
            ));
        }

        // Translate body — simple pass-through with setState substitution
        let body = translate_method_body(&method.body, comp);
        for line in body.lines() {
            out.push_str(&format!("        {}\n", line));
        }

        out.push_str("    }\n");
        Ok(out)
    }

    fn gen_template_nodes(&self, nodes: &[TemplateNode], comp: &Component) -> CompileResult<String> {
        let mut out = String::new();
        for node in nodes {
            out.push_str(&self.gen_template_node(node, comp)?);
        }
        Ok(out)
    }

    fn gen_template_node(&self, node: &TemplateNode, comp: &Component) -> CompileResult<String> {
        match node {
            TemplateNode::Text(t) => Ok(t.clone()),

            TemplateNode::Interpolation(expr) => {
                let js_expr = translate_interpolation(expr, comp);
                Ok(format!("${{{}  }}", js_expr))
            }

            TemplateNode::Element { tag, attrs, children } => {
                let mut out = format!("<{}", tag);

                for attr in attrs {
                    out.push_str(&self.gen_attr(attr, comp)?);
                }

                if children.is_empty() {
                    out.push_str(&format!(" data-el-tag=\"{}\">", tag));
                    out.push_str(&format!("</{}>", tag));
                } else {
                    out.push_str(&format!(" data-el-tag=\"{}\">", tag));
                    out.push_str(&self.gen_template_nodes(children, comp)?);
                    out.push_str(&format!("</{}>", tag));
                }
                Ok(out)
            }

            TemplateNode::Component { name, props } => {
                // Render as inline component call
                let mut prop_entries: Vec<String> = Vec::new();
                for prop in props {
                    match prop {
                        Attr::Static { name: pn, value } => {
                            prop_entries.push(format!("{}: \"{}\"", pn, value));
                        }
                        Attr::Dynamic { name: pn, expr } => {
                            let js = translate_interpolation(expr, comp);
                            prop_entries.push(format!("{}: {}", pn, js));
                        }
                        Attr::EventHandler { event, handler } => {
                            let js = translate_handler(handler, comp);
                            prop_entries.push(format!("on{}: {}", capitalize(event), js));
                        }
                        Attr::BoolAttr { name: pn, expr } => {
                            prop_entries.push(format!("{}: {}", pn, expr));
                        }
                    }
                }
                let props_js = format!("{{ {} }}", prop_entries.join(", "));
                Ok(format!("${{__self._child({}, {})}}", name, props_js))
            }

            TemplateNode::If { condition, then, else_ } => {
                let cond_js = translate_interpolation(condition, comp);
                let then_html = self.gen_template_nodes(then, comp)?;
                let else_html = if let Some(els) = else_ {
                    self.gen_template_nodes(els, comp)?
                } else {
                    String::new()
                };
                Ok(format!(
                    "${{({}) ? `{}` : `{}`}}",
                    cond_js, then_html, else_html
                ))
            }

            TemplateNode::Each { items, item_name, children } => {
                let items_js = translate_interpolation(items, comp);
                let child_html = self.gen_template_nodes(children, comp)?;
                // Generate a map over the array
                Ok(format!(
                    "${{({}).map(({}) => `{}`).join('')}}",
                    items_js, item_name, child_html
                ))
            }

            TemplateNode::Activate { query, result_name, children } => {
                let child_html = self.gen_template_nodes(children, comp)?;
                Ok(format!(
                    "${{((__self._graph.search(\"{}\")) || []).map(({}) => `{}`).join('')}}",
                    query, result_name, child_html
                ))
            }
        }
    }

    fn gen_attr(&self, attr: &Attr, comp: &Component) -> CompileResult<String> {
        match attr {
            Attr::Static { name, value } => {
                Ok(format!(" {}=\"{}\"", name, value))
            }
            Attr::Dynamic { name, expr } => {
                let js = translate_interpolation(expr, comp);
                Ok(format!(" {}=\"${{{}  }}\"", name, js))
            }
            Attr::BoolAttr { name, expr } => {
                let js = translate_interpolation(expr, comp);
                Ok(format!(" ${{({}) ? '{}' : ''  }}", js, name))
            }
            Attr::EventHandler { event, handler } => {
                // We use data attributes to defer event binding
                let js = translate_handler(handler, comp);
                // Inline handler via data attribute — the renderer will bind these
                Ok(format!(" data-el-{}=\"{}\"", event, escape_attr(&js)))
            }
        }
    }
}

/// Translate an el-ui expression to JavaScript.
/// Handles state assignments like `count = count + 1` → `__self.setState('count', count + 1)`
fn translate_interpolation(expr: &str, comp: &Component) -> String {
    translate_expr(expr, comp)
}

fn translate_expr(expr: &str, comp: &Component) -> String {
    let state_names: Vec<&str> = comp.state.iter().map(|s| s.name.as_str()).collect();
    // Arrow functions: passthrough
    // State assignment: `name = value` → `__self.setState('name', value)`
    let trimmed = expr.trim();

    // Check for simple assignment: `ident = expr`
    if let Some(result) = try_translate_assignment(trimmed, &state_names) {
        return result;
    }

    // Arrow function containing assignment: `() => count = count + 1`
    if trimmed.starts_with('(') || trimmed.starts_with("e =>") || trimmed.starts_with("() =>") {
        return translate_arrow_fn(trimmed, &state_names);
    }

    // Otherwise pass through as-is
    trimmed.to_owned()
}

fn try_translate_assignment(expr: &str, state_names: &[&str]) -> Option<String> {
    // Match: `name = value` where name is a state variable
    // Must not be `==` (equality)
    let parts: Vec<&str> = expr.splitn(2, '=').collect();
    if parts.len() == 2 {
        let lhs = parts[0].trim();
        let rhs = parts[1].trim();
        // Ensure it's not `==` or `!=` or `<=` or `>=`
        if !rhs.starts_with('=') && !lhs.ends_with('!') && !lhs.ends_with('<') && !lhs.ends_with('>') {
            if state_names.contains(&lhs) {
                return Some(format!("__self.setState('{}', {})", lhs, rhs));
            }
        }
    }
    None
}

fn translate_arrow_fn(expr: &str, state_names: &[&str]) -> String {
    // Translate assignments inside arrow functions
    // This is a best-effort string transformation
    let mut result = expr.to_owned();
    for name in state_names {
        // Replace `name = ` with `__self.setState('name', ` ... `)` is too complex
        // for a simple string replacement, but we can handle common patterns.
        // Pattern: `name = expr` at end of arrow fn or in braces
        let pat = format!("{} = ", name);
        if let Some(idx) = result.find(&pat) {
            // Check it's not ==
            let after = &result[idx + pat.len()..];
            // Simple case: `() => count = count + 1`
            let prefix = &result[..idx];
            result = format!("{}__self.setState('{}', {})", prefix, name, after.trim_end_matches(')'));
        }
    }
    result
}

fn translate_handler(handler: &str, comp: &Component) -> String {
    translate_expr(handler, comp)
}

/// Translate method body — replace bare state assignments with setState calls.
fn translate_method_body(body: &str, comp: &Component) -> String {
    let state_names: Vec<&str> = comp.state.iter().map(|s| s.name.as_str()).collect();
    let mut lines: Vec<String> = Vec::new();
    for line in body.lines() {
        let trimmed = line.trim();
        if let Some(translated) = try_translate_assignment(trimmed, &state_names) {
            lines.push(format!("{};", translated));
        } else if trimmed.starts_with("return ") {
            lines.push(trimmed.to_owned());
        } else {
            lines.push(trimmed.to_owned());
        }
    }
    lines.join("\n")
}

fn translate_el_to_js(expr: &str) -> String {
    let s = expr.trim();
    // Fn types — translate to null (not a valid JS value, handled at runtime)
    if s.starts_with("Fn") { return "null".into(); }
    // Boolean
    if s == "true" { return "true".into(); }
    if s == "false" { return "false".into(); }
    // String literal
    if s.starts_with('"') { return s.replace('"', "\"").to_owned(); }
    // Numbers
    if s.parse::<i64>().is_ok() { return s.to_owned(); }
    if s.parse::<f64>().is_ok() { return s.to_owned(); }
    // Empty string / void
    if s.is_empty() { return "null".into(); }
    s.to_owned()
}

fn capitalize(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}

fn escape_attr(s: &str) -> String {
    s.replace('"', "&quot;").replace('\'', "&#39;")
}
