//! Kotlin code generation backend

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

/// Kotlin backend for code generation
pub struct KotlinBackend;

/// RFC-0017 Phase A1 helper: replace `mutableListOf<Any?>(<param>(, <param>)*)`
/// or `[<param>(, <param>)*]` substrings with empty-list literals if every
/// element is a known constructor param name. Used by the Kotlin
/// Constructor arm to strip user-arg-bound enter_args / state_args from
/// the bare `init {}` body — those are re-supplied by `__frame_init`.
fn strip_kotlin_param_lists(text: &str, param_names: &[&str]) -> String {
    // The pattern emitted by system_codegen.rs for Kotlin is
    // `mutableListOf<Any?>(seed)` or `mutableListOf<Any?>()`. We match the
    // call-args parens between `mutableListOf<Any?>` and the closing `)`.
    let mut result = String::with_capacity(text.len());
    let bytes = text.as_bytes();
    let needle = b"mutableListOf<Any?>(";
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i..].starts_with(needle) {
            let args_start = i + needle.len();
            let mut depth = 1;
            let mut j = args_start;
            while j < bytes.len() && depth > 0 {
                match bytes[j] {
                    b'(' => depth += 1,
                    b')' => depth -= 1,
                    _ => {}
                }
                if depth == 0 {
                    break;
                }
                j += 1;
            }
            if depth == 0 {
                let inner = &text[args_start..j];
                let parts: Vec<&str> = inner
                    .split(',')
                    .map(|p| p.trim())
                    .filter(|p| !p.is_empty())
                    .collect();
                if !parts.is_empty() && parts.iter().all(|p| param_names.contains(p)) {
                    result.push_str("mutableListOf<Any?>()");
                    i = j + 1;
                    continue;
                }
            }
        }
        result.push(bytes[i] as char);
        i += 1;
    }
    result
}

impl LanguageBackend for KotlinBackend {
    fn emit(&self, node: &CodegenNode, ctx: &mut EmitContext) -> String {
        match node {
            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, .. } => {
                if items.is_empty() {
                    format!("import {}.*", module)
                } else {
                    items
                        .iter()
                        .map(|i| format!("import {}.{}", module, i))
                        .collect::<Vec<_>>()
                        .join("\n")
                }
            }

            CodegenNode::Class {
                name,
                fields,
                methods,
                base_classes,
                is_abstract,
                ..
            } => {
                let mut result = String::new();
                let abstract_kw = if *is_abstract { "abstract " } else { "" };
                let extends = if base_classes.is_empty() {
                    String::new()
                } else {
                    format!(" : {}()", base_classes[0])
                };

                // Kotlin uses a primary constructor declared on the class
                // header. We extract the parameter list from the first
                // Constructor codegen node in `methods` and emit them as
                // `class Robot(val x: Int, val name: String)`.
                //
                // RFC-0017 Phase A1: for SYSTEM classes (not Frame's
                // framework helpers), the primary ctor is BARE — params
                // live on the synthesized `__frame_init` member function
                // and `__create` factory, not on the class header. This
                // decouples Frame init from the language constructor.
                // The Constructor arm reads `ctx.system_name` (set just
                // below) to know whether this Class is the system or a
                // helper, and renders accordingly.
                let is_frame_helper = name.ends_with("FrameEvent")
                    || name.ends_with("FrameContext")
                    || name.ends_with("Compartment");
                let primary_params: Vec<Param> = if is_frame_helper {
                    methods
                        .iter()
                        .find_map(|m| match m {
                            CodegenNode::Constructor { params, .. } => Some(params.clone()),
                            _ => None,
                        })
                        .unwrap_or_default()
                } else {
                    Vec::new()
                };
                let primary_ctor = if primary_params.is_empty() {
                    String::new()
                } else {
                    // Collect domain field names/const-ness to detect collisions.
                    // - Param collides with const field → promote to `val` (the field
                    //   declaration is suppressed below, primary constructor IS the field)
                    // - Param collides with mutable field → bare param (field decl + init body)
                    // - No collision → `val` for accessibility in handler bodies
                    let field_names: std::collections::HashSet<&str> =
                        fields.iter().map(|f| f.name.as_str()).collect();
                    let const_field_names: std::collections::HashSet<&str> = fields
                        .iter()
                        .filter(|f| f.is_const)
                        .map(|f| f.name.as_str())
                        .collect();
                    let params_str = primary_params
                        .iter()
                        .map(|p| {
                            let type_ann = self.map_type(
                                p.type_annotation.as_ref().unwrap_or(&"Any?".to_string()),
                            );
                            let default_suffix = p
                                .default_value
                                .as_ref()
                                .map(|d| format!(" = {}", self.emit(d, ctx)))
                                .unwrap_or_default();
                            if const_field_names.contains(p.name.as_str()) {
                                // const field — primary ctor declares it as val
                                format!("val {}: {}{}", p.name, type_ann, default_suffix)
                            } else if field_names.contains(p.name.as_str()) {
                                // Mutable field collision — bare param
                                format!("{}: {}{}", p.name, type_ann, default_suffix)
                            } else {
                                // No collision — promote to val property
                                format!("val {}: {}{}", p.name, type_ann, default_suffix)
                            }
                        })
                        .collect::<Vec<_>>()
                        .join(", ");
                    format!("({})", params_str)
                };

                // Kotlin: public by default, no access modifier needed
                result.push_str(&format!(
                    "{}{}class {}{}{} {{\n",
                    ctx.get_indent(),
                    abstract_kw,
                    name,
                    primary_ctor,
                    extends
                ));
                ctx.push_indent();

                // Const fields whose name collides with a primary-constructor
                // param are declared as `val name: Type` directly on the class
                // header (above) — skip them in the body.
                let primary_param_names: std::collections::HashSet<&str> =
                    primary_params.iter().map(|p| p.name.as_str()).collect();
                for field in fields {
                    if field.is_const && primary_param_names.contains(field.name.as_str()) {
                        continue;
                    }
                    result.push_str(&self.emit_field(field, ctx));
                }
                if !fields.is_empty() && !methods.is_empty() {
                    result.push('\n');
                }

                // Partition methods: emit non-static first, then group all
                // static methods (and any NativeBlock declarations marked
                // for companion placement, e.g. the @@persist
                // `__skipInitialEnter` flag) inside a single `companion
                // object`. Kotlin permits at most one companion object per
                // class, so every static-like declaration must funnel into
                // the same block.
                let is_companion_native = |n: &CodegenNode| -> bool {
                    if let CodegenNode::NativeBlock { code, .. } = n {
                        // Heuristic: any NativeBlock whose text opens with
                        // `@JvmStatic` is meant for the companion object.
                        // Other NativeBlocks (e.g. Frame-expanded code) stay
                        // at their original position in the class body.
                        code.trim_start().starts_with("@JvmStatic")
                    } else {
                        false
                    }
                };
                let (statics, non_statics): (Vec<_>, Vec<_>) =
                    methods.iter().enumerate().partition(|(_, m)| {
                        matches!(
                            m,
                            CodegenNode::Method {
                                is_static: true,
                                ..
                            }
                        ) || is_companion_native(m)
                    });

                // Temporarily expose this class's name as `system_name` so
                // the Constructor arm can distinguish system vs framework
                // helper classes (FrameEvent / FrameContext / Compartment)
                // and apply the RFC-0017 init-decouple split only to the
                // system class.
                let prev_system = ctx.system_name.clone();
                ctx.system_name = Some(name.clone());

                for (i, (_, method)) in non_statics.iter().enumerate() {
                    if i > 0 {
                        result.push('\n');
                    }
                    result.push_str(&self.emit(method, ctx));
                }

                if !statics.is_empty() {
                    if !non_statics.is_empty() {
                        result.push('\n');
                    }
                    result.push_str(&format!("{}companion object {{\n", ctx.get_indent()));
                    ctx.push_indent();
                    for (i, (_, method)) in statics.iter().enumerate() {
                        if i > 0 {
                            result.push('\n');
                        }
                        result.push_str(&self.emit(method, ctx));
                    }
                    ctx.pop_indent();
                    result.push_str(&format!("{}}}\n", ctx.get_indent()));
                }

                ctx.system_name = prev_system;

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

            CodegenNode::Enum { name, variants } => {
                let mut result = format!("{}enum class {} {{\n", ctx.get_indent(), name);
                ctx.push_indent();
                for (i, variant) in variants.iter().enumerate() {
                    let sep = if i < variants.len() - 1 { "," } else { "" };
                    result.push_str(&format!("{}{}{}\n", ctx.get_indent(), variant.name, sep));
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));
                result
            }

            CodegenNode::Method {
                name,
                params,
                return_type,
                body,
                is_async,
                is_static,
                visibility,
                ..
            } => {
                let vis = self.emit_visibility_kotlin(*visibility);
                let vis_prefix = if vis.is_empty() {
                    String::new()
                } else {
                    format!("{} ", vis)
                };
                let params_str = self.emit_params(params);

                // Kotlin uses "fun" keyword, return type after params with ":"
                let return_str = return_type
                    .as_ref()
                    .map(|t| format!(": {}", self.map_type(t)))
                    .unwrap_or_default();

                // Kotlin async == `suspend fun`. No return-type wrapping —
                // a suspend fun returning T is callable as T from other
                // suspend contexts (coroutines compile to continuations).
                let suspend_prefix = if *is_async { "suspend " } else { "" };

                // Static methods are wrapped in a single `companion object`
                // block by the Class emitter — emit a bare `fun` here.
                let mut result = format!(
                    "{}{}{}fun {}({}){} {{\n",
                    ctx.get_indent(),
                    vis_prefix,
                    suspend_prefix,
                    name,
                    params_str,
                    return_str
                );

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

            CodegenNode::Constructor {
                params,
                body,
                super_call,
            } => {
                // RFC-0017 Phase A1: for SYSTEM classes, split the
                // constructor into:
                //   init { framework_only }
                //   fun __frame_init(<params>): Unit { user $> + cascade }
                //
                // For framework helper classes (FrameEvent / FrameContext
                // / Compartment), keep the original simple `init { body }`
                // emission — they're not user-facing systems.
                //
                // Helper-class detection uses `ctx.system_name`, which the
                // Class arm temporarily sets to the class being emitted.
                let class_name = ctx
                    .system_name
                    .clone()
                    .unwrap_or_else(|| "Class".to_string());
                let is_frame_helper = class_name.ends_with("FrameEvent")
                    || class_name.ends_with("FrameContext")
                    || class_name.ends_with("Compartment");

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

                // System-class path: classify body items.
                //
                // Render once at the body-indent level so the lines can be
                // reused across both `init {}` and `fun __frame_init`.
                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 {
                    framework_lines.push(format!("{}{}", ctx.get_indent(), self.emit(sc, ctx)));
                }
                for stmt in body {
                    let rendered = self.emit(stmt, ctx);
                    // RFC-0020: scope to kernel call + context-stack
                    // mutation. Compartment-init lines (`__compartment =
                    // __prepareEnter(...)`) must still run in `init {}`
                    // so `@@!Foo()` shells are usable.
                    let frame_init_only = rendered.contains("__kernel(")
                        || rendered.contains("_context_stack.add(")
                        || rendered.contains("_context_stack.removeAt(")
                        || rendered.contains("_context_stack.removeLast(");
                    if frame_init_only {
                        frame_init_lines.push(rendered);
                        continue;
                    }
                    let mentions_param = param_names.iter().any(|p| {
                        rendered
                            .split(|c: char| !c.is_alphanumeric() && c != '_')
                            .any(|w| w == *p)
                    });
                    if mentions_param {
                        frame_init_lines.push(rendered.clone());
                        // RFC-0017: strip handles `mutableListOf(seed)`
                        // → empty in prepareEnter args. For plain
                        // `this.field = seed` strip is a no-op;
                        // emitting it into the no-arg bare ctor would
                        // refuse to compile. Skip when a param ref
                        // survives the strip.
                        let stripped = strip_kotlin_param_lists(&rendered, &param_names);
                        let still_refs_param = param_names.iter().any(|p| {
                            stripped
                                .split(|c: char| !c.is_alphanumeric() && c != '_')
                                .any(|w| w == *p)
                        });
                        if !still_refs_param {
                            framework_lines.push(stripped);
                        }
                    } else {
                        framework_lines.push(rendered);
                    }
                }
                ctx.pop_indent();

                // Emit `init { framework_lines }`
                let mut result = format!("{}init {{\n", ctx.get_indent());
                ctx.push_indent();
                for line in &framework_lines {
                    result.push_str(line);
                    if !line.ends_with('\n') {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                // Emit `fun __frame_init(<params>)` member function
                result.push('\n');
                let frame_init_params = self.emit_params(params);
                result.push_str(&format!(
                    "{}fun __frame_init({}) {{\n",
                    ctx.get_indent(),
                    frame_init_params
                ));
                ctx.push_indent();
                for line in &frame_init_lines {
                    result.push_str(line);
                    if !line.ends_with('\n') {
                        result.push('\n');
                    }
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}\n", ctx.get_indent()));

                result
            }

            CodegenNode::VarDecl {
                name,
                type_annotation,
                init,
                is_const,
            } => {
                let decl_kw = if *is_const { "val" } else { "var" };
                let type_str = type_annotation
                    .as_ref()
                    .map(|t| format!(": {}", self.map_type(t)))
                    .unwrap_or_default();
                if let Some(init_expr) = init {
                    format!(
                        "{}{} {}{} = {}",
                        ctx.get_indent(),
                        decl_kw,
                        name,
                        type_str,
                        self.emit(init_expr, ctx)
                    )
                } else {
                    format!("{}{} {}{}", ctx.get_indent(), decl_kw, name, type_str)
                }
            }

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

            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 = format!(
                    "{}if ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(condition, ctx)
                );
                ctx.push_indent();
                for stmt in then_block {
                    result.push_str(&self.emit(stmt, ctx));
                    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));
                        result.push('\n');
                    }
                    ctx.pop_indent();
                }
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::Match { scrutinee, arms } => {
                // Kotlin uses "when" instead of "switch"
                let mut result = format!(
                    "{}when ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(scrutinee, ctx)
                );
                ctx.push_indent();
                for arm in arms {
                    result.push_str(&format!(
                        "{}{} -> {{\n",
                        ctx.get_indent(),
                        self.emit(&arm.pattern, ctx)
                    ));
                    ctx.push_indent();
                    for stmt in &arm.body {
                        result.push_str(&self.emit(stmt, ctx));
                        result.push('\n');
                    }
                    ctx.pop_indent();
                    result.push_str(&format!("{}}}\n", ctx.get_indent()));
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::While { condition, body } => {
                let mut result = format!(
                    "{}while ({}) {{\n",
                    ctx.get_indent(),
                    self.emit(condition, ctx)
                );
                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    result.push('\n');
                }
                ctx.pop_indent();
                result.push_str(&format!("{}}}", ctx.get_indent()));
                result
            }

            CodegenNode::For {
                var,
                iterable,
                body,
            } => {
                // Kotlin: for (item in collection)
                let mut result = format!(
                    "{}for ({} in {}) {{\n",
                    ctx.get_indent(),
                    var,
                    self.emit(iterable, ctx)
                );
                ctx.push_indent();
                for stmt in body {
                    result.push_str(&self.emit(stmt, ctx));
                    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) => 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(),
            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 args_str: Vec<String> = args.iter().map(|a| self.emit(a, ctx)).collect();
                format!("{}({})", self.emit(target, ctx), args_str.join(", "))
            }

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

            CodegenNode::FieldAccess { object, field } => {
                format!("{}.{}", self.emit(object, ctx), field)
            }
            // Kotlin: use indexer syntax obj[index]
            CodegenNode::IndexAccess { object, index } => {
                format!("{}[{}]", self.emit(object, ctx), self.emit(index, ctx))
            }
            CodegenNode::SelfRef => "this".to_string(),

            CodegenNode::Array(elements) => {
                let elems: Vec<String> = elements.iter().map(|e| self.emit(e, ctx)).collect();
                if elems.is_empty() {
                    "mutableListOf<Any?>()".to_string()
                } else {
                    format!("mutableListOf({})", elems.join(", "))
                }
            }

            CodegenNode::Dict(pairs) => {
                if pairs.is_empty() {
                    "mutableMapOf<String, Any?>()".to_string()
                } else {
                    let pairs_str: Vec<String> = pairs
                        .iter()
                        .map(|(k, v)| format!("{} to {}", self.emit(k, ctx), self.emit(v, ctx)))
                        .collect();
                    format!("mutableMapOf({})", pairs_str.join(", "))
                }
            }

            CodegenNode::Ternary {
                condition,
                then_expr,
                else_expr,
            } => {
                // Kotlin: if-else expression (no ternary operator)
                format!(
                    "if ({}) {} else {}",
                    self.emit(condition, ctx),
                    self.emit(then_expr, ctx),
                    self.emit(else_expr, ctx)
                )
            }

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

            // Kotlin: "expr as Type" for cast
            CodegenNode::Cast { expr, target_type } => {
                format!("{} as {}", self.emit(expr, ctx), target_type)
            }

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

            // Frame-specific (expanded upstream as NativeBlock in normal pipeline)
            CodegenNode::Transition {
                target_state,
                indent,
                ..
            } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!(
                    "{}this.__transition({}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.add(this.__compartment.copy())", ind)
            }
            CodegenNode::StackPop { indent } => {
                let ind = format!("{}{}", ctx.get_indent(), " ".repeat(*indent));
                format!(
                    "{}this.__transition(this._state_stack.removeAt(this._state_stack.size - 1))",
                    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(", ")
                    )
                }
            }

            CodegenNode::NativeBlock { code, .. } => {
                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 runtime_imports(&self) -> Vec<String> {
        vec![]
    }

    fn emit_module_imports(
        &self,
        imports: &[crate::frame_c::compiler::frame_ast::Import],
    ) -> Vec<String> {
        // RFC-0022 — emit one `import x.Counter` per `@@system` name
        // surfaced by the importer's peek; fall back to wildcard
        // `import x.*` when the peek found nothing.
        let mut out: Vec<String> = Vec::new();
        for imp in imports {
            let path = imp.module.as_str();
            if path.is_empty() {
                continue;
            }
            let stem = match path.rfind('.') {
                Some(idx) => &path[..idx],
                None => path,
            };
            let pkg = stem.trim_start_matches("./").replace('/', ".");
            if pkg.is_empty() {
                continue;
            }
            if imp.symbols.is_empty() {
                out.push(format!("import {}.*", pkg));
            } else {
                for sym in &imp.symbols {
                    out.push(format!("import {}.{}", pkg, sym));
                }
            }
        }
        out
    }

    fn class_syntax(&self) -> ClassSyntax {
        ClassSyntax::kotlin()
    }
    fn target_language(&self) -> TargetLanguage {
        TargetLanguage::Kotlin
    }
    fn null_keyword(&self) -> &'static str {
        "null"
    }
}

impl KotlinBackend {
    fn emit_params(&self, params: &[Param]) -> String {
        params
            .iter()
            .map(|p| {
                let type_ann =
                    self.map_type(p.type_annotation.as_ref().unwrap_or(&"Any?".to_string()));
                format!("{}: {}", p.name, type_ann)
            })
            .collect::<Vec<_>>()
            .join(", ")
    }

    /// Kotlin visibility: public is default (omit), private and protected are explicit
    fn emit_visibility_kotlin(&self, vis: Visibility) -> &'static str {
        match vis {
            Visibility::Public => "", // public is default in Kotlin
            Visibility::Private => "private",
            Visibility::Protected => "protected",
        }
    }

    fn map_type(&self, t: &str) -> String {
        match t {
            "Any" | "Object" | "object" => "Any?".to_string(),
            "string" | "str" => "String".to_string(),
            "String" => "String".to_string(),
            "int" | "i32" | "i64" | "number" => "Int".to_string(),
            "float" | "f64" | "f32" | "double" => "Double".to_string(),
            "bool" | "boolean" => "Boolean".to_string(),
            "Boolean" => "Boolean".to_string(),
            "void" => "Unit".to_string(),
            "var" => "Any?".to_string(),
            other => other.to_string(),
        }
    }

    /// Type-appropriate placeholder used when a Public domain field has
    /// no declaration-scope initializer (because it was stripped to the
    /// constructor body to take a system-param value). Kotlin requires
    /// every property to be initialized; the placeholder is overwritten
    /// by the `init { }` block.
    fn kotlin_default_for_type(&self, type_str: &str) -> &'static str {
        match type_str {
            "Int" | "Long" | "Short" | "Byte" => "0",
            "Float" | "Double" => "0.0",
            "Boolean" => "false",
            "String" => "\"\"",
            _ => "null",
        }
    }

    /// Emit a single Kotlin class-property declaration line:
    ///   `<indent>[<vis> ][val|var ]<name>[: <type>][ = <init>]\n`
    ///
    /// Type slot:
    /// - present → emit `: <type>` (after `map_type`)
    /// - absent → omit; Kotlin infers from the initializer
    ///
    /// Initializer policy:
    /// - structured `initializer` present → use it verbatim
    /// - missing AND visibility is `Public` AND type is present → emit
    ///   a type-appropriate placeholder (e.g. `0`, `""`) so Kotlin's
    ///   "must be initialized" rule is satisfied; the constructor body
    ///   overwrites with the real value
    /// - missing AND non-`Public` (auto-generated kernel field) → emit
    ///   no init; the `init { }` block performs the assignment
    ///
    /// Visibility `Public` is Kotlin's default and is OMITTED.
    fn emit_field(&self, field: &Field, ctx: &mut EmitContext) -> String {
        let vis = self.emit_visibility_kotlin(field.visibility);
        let var_kw = if field.is_const { "val " } else { "var " };
        let type_str_opt = field.type_annotation.as_ref().map(|t| self.map_type(t));
        let type_suffix = match &type_str_opt {
            Some(t) => format!(": {}", t),
            None => String::new(),
        };
        let init_suffix = match (&field.initializer, field.visibility, &type_str_opt) {
            (Some(init), _, _) => format!(" = {}", self.emit(init, ctx)),
            (None, Visibility::Public, Some(t)) => {
                format!(" = {}", self.kotlin_default_for_type(t))
            }
            (None, _, _) => String::new(),
        };
        let comments = field.format_leading_comments(&ctx.get_indent());
        if vis.is_empty() {
            format!(
                "{}{}{}{}{}{}\n",
                comments,
                ctx.get_indent(),
                var_kw,
                field.name,
                type_suffix,
                init_suffix
            )
        } else {
            format!(
                "{}{}{} {}{}{}{}\n",
                comments,
                ctx.get_indent(),
                vis,
                var_kw,
                field.name,
                type_suffix,
                init_suffix
            )
        }
    }
}
