//! PHP code generation backend
//!
//! PHP is class-based with `$this->` member access, dynamic typing.
//! - `class SystemName { ... }`
//! - Constructor: `public function __construct() { ... }`
//! - Methods: `public function methodName($param) { ... }`
//! - Fields: `private $__compartment;`
//! - All variables prefixed with `$`: `$this->field`, `$var`
//! - Member access: `$this->method()`, `$this->field`
//! - `null` for null, `[]` for arrays/dicts

use crate::frame_c::compiler::codegen::ast::*;
use crate::frame_c::compiler::codegen::backend::*;
use crate::frame_c::visitors::TargetLanguage;

/// PHP backend for code generation
pub struct PhpBackend;

impl LanguageBackend for PhpBackend {
    fn emit(&self, node: &CodegenNode, ctx: &mut EmitContext) -> String {
        match node {
            // ===== Structural =====
            CodegenNode::Module { imports, items } => {
                let mut result = String::new();
                for import in imports {
                    result.push_str(&self.emit(import, ctx));
                    result.push('\n');
                }
                if !imports.is_empty() && !items.is_empty() {
                    result.push('\n');
                }
                for (i, item) in items.iter().enumerate() {
                    if i > 0 {
                        result.push_str("\n\n");
                    }
                    result.push_str(&self.emit(item, ctx));
                }
                result
            }

            CodegenNode::Import {
                module,
                items,
                alias,
            } => {
                if items.is_empty() {
                    if let Some(alias) = alias {
                        format!("use {} as {};", module, alias)
                    } else {
                        format!("use {};", module)
                    }
                } else {
                    format!("use {}\\{{{}}};", module, items.join(", "))
                }
            }

            CodegenNode::Class {
                name,
                fields,
                methods,
                base_classes,
                is_abstract: _,
                ..
            } => {
                let mut result = String::new();

                let extends = if base_classes.is_empty() {
                    String::new()
                } else {
                    format!(" extends {}", base_classes[0])
                };

                result.push_str(&format!(
                    "{}class {}{} {{\n",
                    ctx.get_indent(),
                    name,
                    extends
                ));
                ctx.push_indent();

                // Fields
                for field in fields {
                    result.push_str(&self.emit_field(field, ctx));
                }
                if !fields.is_empty() && !methods.is_empty() {
                    result.push('\n');
                }

                // RFC-0017 Phase A5: temporarily set system_name for
                // Constructor arm helper-class detection.
                let prev_system = ctx.system_name.clone();
                ctx.system_name = Some(name.clone());
                for (i, method) in methods.iter().enumerate() {
                    if i > 0 {
                        result.push('\n');
                    }
                    result.push_str(&self.emit(method, ctx));
                }
                ctx.system_name = prev_system;

                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));
                result
            }

            CodegenNode::Enum { name, variants } => {
                // PHP 8.1+ enums or class constants
                let mut result = format!("{}class {} {{\n", ctx.get_indent(), name);
                ctx.push_indent();
                for variant in variants {
                    if let Some(ref value) = variant.value {
                        result.push_str(&format!(
                            "{}const {} = {};\n",
                            ctx.get_indent(),
                            variant.name,
                            self.emit(value, ctx)
                        ));
                    } else {
                        result.push_str(&format!("{}const {};\n", ctx.get_indent(), variant.name));
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));
                result
            }

            // ===== Methods =====
            CodegenNode::Method {
                name,
                params,
                body,
                is_async: _,
                is_static,
                visibility,
                return_type: _,
                decorators: _,
            } => {
                let vis = self.emit_visibility(*visibility);
                let static_kw = if *is_static { "static " } else { "" };
                let params_str = self.emit_params(params);

                let mut result = format!(
                    "{}{} {}function {}({}) {{\n",
                    ctx.get_indent(),
                    vis,
                    static_kw,
                    name,
                    params_str
                );

                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));
                result
            }

            CodegenNode::Constructor {
                params,
                body,
                super_call,
            } => {
                // RFC-0017 Phase A5: split system class into bare
                // __construct + _frame_init + static _create factory.
                let class_name = ctx.system_name.clone().unwrap_or_default();
                let is_frame_helper = class_name.ends_with("FrameEvent")
                    || class_name.ends_with("FrameContext")
                    || class_name.ends_with("Compartment");

                if is_frame_helper {
                    let params_str = self.emit_params(params);
                    let mut result = format!(
                        "{}public function __construct({}) {{\n",
                        ctx.get_indent(),
                        params_str
                    );
                    ctx.push_indent();
                    if let Some(sc) = super_call {
                        result.push_str(&self.emit(sc, ctx));
                        result.push_str(";\n");
                    }
                    for stmt in body {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    ctx.pop_indent();
                    result.push_str(&format!("{}}}\n", ctx.get_indent()));
                    return result;
                }

                let param_names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
                ctx.push_indent();
                let mut framework_lines: Vec<String> = Vec::new();
                let mut frame_init_lines: Vec<String> = Vec::new();
                if let Some(sc) = super_call {
                    let s = self.emit(sc, ctx);
                    framework_lines.push(format!("{};\n", s));
                }
                for stmt in body {
                    let mut rendered = self.emit(stmt, ctx);
                    if self.needs_semicolon(stmt) && !rendered.ends_with(";\n") {
                        if !rendered.ends_with('\n') {
                            rendered.push_str(";\n");
                        } else {
                            let trimmed = rendered.trim_end_matches('\n').to_string();
                            rendered = format!("{};\n", trimmed);
                        }
                    } else if !rendered.ends_with('\n') {
                        rendered.push('\n');
                    }
                    // RFC-0020: scope to kernel call + context-stack mutation.
                    let frame_init_only = rendered.contains("$this->__kernel(")
                        || rendered.contains("$this->_context_stack[]")
                        || rendered.contains("array_pop($this->_context_stack");
                    if frame_init_only {
                        frame_init_lines.push(rendered);
                        continue;
                    }
                    // PHP variables have `$` prefix; param names in source
                    // are without prefix. Check for both forms.
                    let mentions_param = param_names.iter().any(|p| {
                        let with_dollar = format!("${}", p);
                        rendered
                            .split(|c: char| !c.is_alphanumeric() && c != '_' && c != '$')
                            .any(|w| w == *p || w == with_dollar.as_str())
                    });
                    if mentions_param {
                        frame_init_lines.push(rendered);
                    } else {
                        framework_lines.push(rendered);
                    }
                }
                ctx.pop_indent();

                let mut result = format!("{}public function __construct() {{\n", ctx.get_indent());
                for line in &framework_lines {
                    result.push_str(line);
                }
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                result.push('\n');
                let frame_init_params = self.emit_params(params);
                result.push_str(&format!(
                    "{}public function _frame_init({}): void {{\n",
                    ctx.get_indent(),
                    frame_init_params
                ));
                for line in &frame_init_lines {
                    result.push_str(line);
                }
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                result.push('\n');
                let create_params = self.emit_params(params);
                result.push_str(&format!(
                    "{}public static function _create({}): self {{\n",
                    ctx.get_indent(),
                    create_params
                ));
                ctx.push_indent();
                result.push_str(&format!("{}$c = new self();\n", ctx.get_indent()));
                let arg_pass: Vec<String> = params.iter().map(|p| format!("${}", p.name)).collect();
                result.push_str(&format!(
                    "{}$c->_frame_init({});\n",
                    ctx.get_indent(),
                    arg_pass.join(", ")
                ));
                result.push_str(&format!("{}return $c;\n", ctx.get_indent()));
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                result
            }

            // ===== Statements =====
            CodegenNode::VarDecl {
                name,
                init,
                is_const: _,
                type_annotation: _,
            } => {
                // PHP local vars: $name = value
                if let Some(init_expr) = init {
                    let init_str = self.emit(init_expr, ctx);
                    format!("{}${} = {}", ctx.get_indent(), name, init_str)
                } else {
                    format!("{}${}", ctx.get_indent(), name)
                }
            }

            CodegenNode::Assignment { target, value } => {
                let target_str = self.emit(target, ctx);
                let value_str = self.emit(value, ctx);
                format!("{}{} = {}", ctx.get_indent(), target_str, value_str)
            }

            CodegenNode::Return { value } => {
                if let Some(val) = value {
                    format!("{}return {}", ctx.get_indent(), self.emit(val, ctx))
                } else {
                    format!("{}return", ctx.get_indent())
                }
            }

            CodegenNode::If {
                condition,
                then_block,
                else_block,
            } => {
                let mut result = String::new();
                let cond_str = self.emit(condition, ctx);
                result.push_str(&format!("{}if ({}) {{\n", ctx.get_indent(), cond_str));

                ctx.push_indent();
                for stmt in then_block {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();

                if let Some(else_stmts) = else_block {
                    result.push_str(&format!("{}}} else {{\n", ctx.get_indent()));
                    ctx.push_indent();
                    for stmt in else_stmts {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    ctx.pop_indent();
                }

                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::Match { scrutinee, arms } => {
                let mut result = String::new();
                let scrutinee_str = self.emit(scrutinee, ctx);
                result.push_str(&format!(
                    "{}switch ({}) {{\n",
                    ctx.get_indent(),
                    scrutinee_str
                ));

                ctx.push_indent();
                for arm in arms {
                    let pattern_str = self.emit(&arm.pattern, ctx);
                    result.push_str(&format!("{}case {}:\n", ctx.get_indent(), pattern_str));

                    ctx.push_indent();
                    for stmt in &arm.body {
                        result.push_str(&self.emit(stmt, ctx));
                        if self.needs_semicolon(stmt) {
                            result.push_str(";\n");
                        } else {
                            result.push('\n');
                        }
                    }
                    result.push_str(&format!("{}break;\n", ctx.get_indent()));
                    ctx.pop_indent();
                }
                ctx.pop_indent();

                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::While { condition, body } => {
                let mut result = String::new();
                let cond_str = self.emit(condition, ctx);
                result.push_str(&format!("{}while ({}) {{\n", ctx.get_indent(), cond_str));

                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();

                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::For {
                var,
                iterable,
                body,
            } => {
                let mut result = String::new();
                let iter_str = self.emit(iterable, ctx);
                result.push_str(&format!(
                    "{}foreach ({} as ${}) {{\n",
                    ctx.get_indent(),
                    iter_str,
                    var
                ));

                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    if self.needs_semicolon(stmt) {
                        result.push_str(";\n");
                    } else {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();

                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::Break => format!("{}break", ctx.get_indent()),
            CodegenNode::Continue => format!("{}continue", ctx.get_indent()),

            CodegenNode::ExprStmt(expr) => {
                format!("{}{}", ctx.get_indent(), self.emit(expr, ctx))
            }

            CodegenNode::Await(expr) => {
                // PHP doesn't have native async/await (frameworks handle this)
                self.emit(expr, ctx)
            }

            CodegenNode::Comment { text, is_doc } => {
                if *is_doc {
                    format!("{}/** {} */", ctx.get_indent(), text)
                } else {
                    format!("{}// {}", ctx.get_indent(), text)
                }
            }

            CodegenNode::Empty => String::new(),

            // ===== Expressions =====
            CodegenNode::Ident(name) => name.clone(),

            CodegenNode::Literal(lit) => self.emit_literal(lit, ctx),

            CodegenNode::BinaryOp { op, left, right } => self.emit_binary_op(op, left, right, ctx),

            CodegenNode::UnaryOp { op, operand } => self.emit_unary_op(op, operand, ctx),

            CodegenNode::Call { target, args } => {
                let target_str = self.emit(target, ctx);
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("{}({})", target_str, args_str.join(", "))
            }

            CodegenNode::MethodCall {
                object,
                method,
                args,
            } => {
                let obj_str = self.emit(object, ctx);
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("{}->{}({})", obj_str, method, args_str.join(", "))
            }

            CodegenNode::FieldAccess { object, field } => {
                let obj_str = self.emit(object, ctx);
                format!("{}->{}", obj_str, field)
            }

            CodegenNode::IndexAccess { object, index } => {
                let obj_str = self.emit(object, ctx);
                let idx_str = self.emit(index, ctx);
                format!("{}[{}]", obj_str, idx_str)
            }

            CodegenNode::SelfRef => "$this".to_string(),

            CodegenNode::Array(elements) => {
                let elems: Vec<String> = elements.iter().map(|e| self.emit(e, ctx)).collect();
                format!("[{}]", elems.join(", "))
            }

            CodegenNode::Dict(pairs) => {
                let pairs_str: Vec<String> = pairs
                    .iter()
                    .map(|(k, v)| format!("{} => {}", self.emit(k, ctx), self.emit(v, ctx)))
                    .collect();
                format!("[{}]", pairs_str.join(", "))
            }

            CodegenNode::Ternary {
                condition,
                then_expr,
                else_expr,
            } => {
                let cond = self.emit(condition, ctx);
                let then_val = self.emit(then_expr, ctx);
                let else_val = self.emit(else_expr, ctx);
                format!("{} ? {} : {}", cond, then_val, else_val)
            }

            CodegenNode::Lambda { params, body } => {
                let params_str = params
                    .iter()
                    .map(|p| format!("${}", p.name))
                    .collect::<Vec<_>>()
                    .join(", ");
                let body_str = self.emit(body, ctx);
                format!("function({}) {{ return {}; }}", params_str, body_str)
            }

            CodegenNode::Cast { expr, target_type } => {
                format!("({}){}", target_type, self.emit(expr, ctx))
            }

            CodegenNode::New { class, args } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("new {}({})", class, args_str.join(", "))
            }

            // ===== Frame-Specific =====
            CodegenNode::Transition {
                target_state,
                indent,
                ..
            } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!(
                    "{}$this->__transition(new {}Compartment({}))",
                    ind,
                    ctx.system_name.as_deref().unwrap_or(""),
                    target_state
                )
            }

            CodegenNode::ChangeState {
                target_state,
                indent,
                ..
            } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}$this->_changeState($this->{})", ind, target_state)
            }

            CodegenNode::Forward { indent, .. } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}return", ind)
            }

            CodegenNode::StackPush { indent } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!(
                    "{}$this->_state_stack[] = $this->__compartment->copy()",
                    ind
                )
            }

            CodegenNode::StackPop { indent } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!("{}$this->__transition(array_pop($this->_state_stack))", ind)
            }

            CodegenNode::StateContext { state_name } => {
                format!("$this->_stateContext[\"{}\"]", state_name)
            }

            CodegenNode::SendEvent { event, args } => {
                let args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                if args_str.is_empty() {
                    format!("{}$this->{}()", ctx.get_indent(), event)
                } else {
                    format!(
                        "{}$this->{}({})",
                        ctx.get_indent(),
                        event,
                        args_str.join(", ")
                    )
                }
            }

            // ===== Native Code Preservation =====
            CodegenNode::NativeBlock { code, span: _ } => {
                let indent = ctx.get_indent();
                code.lines()
                    .map(|line| {
                        if line.trim().is_empty() {
                            String::new()
                        } else {
                            format!("{}{}", indent, line)
                        }
                    })
                    .collect::<Vec<_>>()
                    .join("\n")
            }

            CodegenNode::SplicePoint { id } => {
                format!("// SPLICE_POINT: {}", id)
            }
        }
    }

    fn emit_module_imports(
        &self,
        imports: &[crate::frame_c::compiler::frame_ast::Import],
    ) -> Vec<String> {
        // RFC-0022 Phase 1 lax — `require_once` brings the file's class
        // definitions into the global require chain. PHP classes are
        // globally accessible after include, so bare `new Counter()`
        // resolves once the file is loaded.
        imports
            .iter()
            .filter_map(|imp| {
                let path = imp.module.as_str();
                if path.is_empty() {
                    return None;
                }
                let stem = match path.rfind('.') {
                    Some(idx) => &path[..idx],
                    None => path,
                };
                Some(format!("require_once __DIR__ . '/{}.php';", stem))
            })
            .collect()
    }

    fn runtime_imports(&self) -> Vec<String> {
        // PHP source files must begin with `<?php`, otherwise the PHP
        // interpreter treats the file as plain HTML and echoes it
        // verbatim instead of executing it. Emitted here at the
        // runtime-imports slot so the assembler places it ahead of
        // every other emitted line. (FRAMEC_BUGS.md Issue #9.)
        vec!["<?php".to_string()]
    }

    fn class_syntax(&self) -> ClassSyntax {
        ClassSyntax::php()
    }

    fn target_language(&self) -> TargetLanguage {
        TargetLanguage::Php
    }

    fn null_keyword(&self) -> &'static str {
        "null"
    }
}

impl PhpBackend {
    fn emit_params(&self, params: &[Param]) -> String {
        params
            .iter()
            .map(|p| {
                let mut s = format!("${}", p.name);
                if let Some(ref d) = p.default_value {
                    let mut ctx = EmitContext::new();
                    s.push_str(&format!(" = {}", self.emit(d, &mut ctx)));
                }
                s
            })
            .collect::<Vec<_>>()
            .join(", ")
    }

    fn emit_visibility(&self, vis: Visibility) -> &'static str {
        match vis {
            Visibility::Public => "public",
            Visibility::Private => "private",
            Visibility::Protected => "protected",
        }
    }

    fn needs_semicolon(&self, node: &CodegenNode) -> bool {
        !matches!(
            node,
            CodegenNode::If { .. }
                | CodegenNode::While { .. }
                | CodegenNode::For { .. }
                | CodegenNode::Match { .. }
                | CodegenNode::Comment { .. }
                | CodegenNode::NativeBlock { .. }
                | CodegenNode::Empty
        )
    }

    /// Emit a single PHP class-property declaration line:
    ///   `<indent><vis> [static ]$<name>[ = <init>];\n`
    ///
    /// PHP property declarations don't carry type annotations (the
    /// codegen could opt into PHP 7.4+ typed properties later but the
    /// current `synthesize_field_raw` output for PHP omits the type
    /// entirely — preserved here for byte-identical migration).
    fn emit_field(&self, field: &Field, ctx: &mut EmitContext) -> String {
        let vis = self.emit_visibility(field.visibility);
        let static_kw = if field.is_static { "static " } else { "" };
        let init_suffix = match &field.initializer {
            Some(init) => format!(" = {}", self.emit(init, ctx)),
            None => String::new(),
        };
        let comments = field.format_leading_comments(&ctx.get_indent());
        format!(
            "{}{}{} {}${}{};\n",
            comments,
            ctx.get_indent(),
            vis,
            static_kw,
            field.name,
            init_suffix
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_emit_literal() {
        let backend = PhpBackend;
        let mut ctx = EmitContext::new();

        assert_eq!(backend.emit(&CodegenNode::int(42), &mut ctx), "42");
        assert_eq!(backend.emit(&CodegenNode::bool(true), &mut ctx), "true");
        assert_eq!(backend.emit(&CodegenNode::null(), &mut ctx), "null");
    }

    #[test]
    fn test_emit_field_access() {
        let backend = PhpBackend;
        let mut ctx = EmitContext::new();

        let node = CodegenNode::field(CodegenNode::self_ref(), "_state");
        assert_eq!(backend.emit(&node, &mut ctx), "$this->_state");
    }

    #[test]
    fn test_emit_method_call() {
        let backend = PhpBackend;
        let mut ctx = EmitContext::new();

        let node = CodegenNode::MethodCall {
            object: Box::new(CodegenNode::self_ref()),
            method: "doSomething".to_string(),
            args: vec![CodegenNode::int(42)],
        };
        assert_eq!(backend.emit(&node, &mut ctx), "$this->doSomething(42)");
    }

    #[test]
    fn test_emit_self_ref() {
        let backend = PhpBackend;
        let mut ctx = EmitContext::new();

        assert_eq!(backend.emit(&CodegenNode::self_ref(), &mut ctx), "$this");
    }
}
