// native_proto/codegen/helpers.rs
// Type inference helpers, operator mappings, and output helpers.

use super::Codegen;
use oxc_ast::ast::*;

// ── Zig reserved keyword detection ──────────────────

/// Zig 0.16 reserved keywords. JS identifiers that collide with these
/// must be renamed (prefixed with `_`) to produce valid Zig code.
const ZIG_RESERVED_KEYWORDS: &[&str] = &[
    "addrspace",
    "align",
    "allowzero",
    "and",
    "anyframe",
    "anytype",
    "asm",
    "async",
    "await",
    "break",
    "callconv",
    "catch",
    "comptime",
    "const",
    "continue",
    "defer",
    "else",
    "enum",
    "errdefer",
    "error",
    "export",
    "extern",
    "fn",
    "for",
    "if",
    "inline",
    "linksection",
    "noalias",
    "noinline",
    "nosuspend",
    "opaque",
    "or",
    "orelse",
    "packed",
    "pub",
    "resume",
    "return",
    "struct",
    "suspend",
    "switch",
    "test",
    "threadlocal",
    "try",
    "union",
    "unreachable",
    "usingnamespace",
    "var",
    "volatile",
    "while",
];

/// Convert a JS identifier to a Zig-safe identifier.
/// Pre-pends `_` to avoid collisions with Zig reserved keywords.
pub(crate) fn zig_safe_name(name: &str) -> String {
    if ZIG_RESERVED_KEYWORDS.contains(&name) {
        format!("_{}", name)
    } else {
        name.to_string()
    }
}

// ── Helpers (methods) ──────────────────────────────

impl Codegen {
    /// Convenience method: convert a JS identifier to a Zig-safe identifier.
    /// Also checks `shadow_renames` to rewrite shadowed variables to their
    /// renamed counterpart (e.g. `x` → `x_1` if shadowed in a nested block).
    pub(crate) fn zig_safe_name(&self, name: &str) -> String {
        // Check shadow renames from innermost scope outward.
        for scope in self.shadow_renames.iter().rev() {
            if let Some(renamed) = scope.get(name) {
                return renamed.clone();
            }
        }
        zig_safe_name(name)
    }

    /// Push a new shadowing scope onto the rename stack.
    /// Called when entering a nested block — any variable shadowing detected
    /// within this scope will be recorded in the pushed HashMap.
    pub(crate) fn push_shadow_scope(&mut self) {
        self.shadow_renames.push(std::collections::HashMap::new());
    }

    /// Pop the innermost shadowing scope from the rename stack.
    /// Called when leaving a nested block — shadowed variable renames are
    /// discarded since they only apply within that block's scope.
    pub(crate) fn pop_shadow_scope(&mut self) {
        self.shadow_renames.pop();
    }

    pub(crate) fn binding_name<'a>(&self, pattern: &BindingPattern<'a>) -> Option<&'a str> {
        match pattern {
            BindingPattern::BindingIdentifier(id) => Some(id.name.as_str()),
            _ => None,
        }
    }

    /// Extract a property key name from a PropertyKey for destructuring.
    /// Returns None for computed keys (not yet supported).
    pub(crate) fn property_key_name(&self, key: &PropertyKey) -> Option<String> {
        match key {
            PropertyKey::StaticIdentifier(id) => Some(id.name.to_string()),
            PropertyKey::PrivateIdentifier(id) => Some(id.name.to_string()),
            _ => None,
        }
    }

    /// Resolve a destructuring binding pattern, returning (name, optional_default_expr).
    /// Handles both plain bindings and `BindingPattern::AssignmentPattern` (with default).
    pub(crate) fn destructure_binding<'a>(
        &mut self,
        pattern: &BindingPattern<'a>,
    ) -> Option<(&'a str, Option<String>)> {
        match pattern {
            BindingPattern::BindingIdentifier(id) => Some((id.name.as_str(), None)),
            BindingPattern::AssignmentPattern(ap) => {
                let name = self.binding_name(&ap.left)?;
                let default_str = self.emit_expr_to_string(&ap.right);
                Some((name, Some(default_str)))
            }
            _ => None,
        }
    }

    pub(crate) fn binary_op(op: BinaryOperator) -> &'static str {
        match op {
            BinaryOperator::Addition => "+",
            BinaryOperator::Subtraction => "-",
            BinaryOperator::Multiplication => "*",
            BinaryOperator::Division => "/",
            BinaryOperator::Remainder => "%",
            BinaryOperator::LessThan => "<",
            BinaryOperator::GreaterThan => ">",
            BinaryOperator::LessEqualThan => "<=",
            BinaryOperator::GreaterEqualThan => ">=",
            BinaryOperator::Equality => "==",
            BinaryOperator::Inequality => "!=",
            BinaryOperator::StrictEquality => "==",
            BinaryOperator::StrictInequality => "!=",
            BinaryOperator::ShiftLeft => "<<",
            BinaryOperator::ShiftRight => ">>",
            BinaryOperator::ShiftRightZeroFill => ">>",
            BinaryOperator::BitwiseAnd => "&",
            BinaryOperator::BitwiseOR => "|",
            BinaryOperator::BitwiseXOR => "^",
            _ => "/* op */",
        }
    }

    pub(crate) fn assignment_op(op: AssignmentOperator) -> &'static str {
        match op {
            AssignmentOperator::Assign => "=",
            AssignmentOperator::Addition => "+=",
            AssignmentOperator::Subtraction => "-=",
            AssignmentOperator::Multiplication => "*=",
            AssignmentOperator::Division => "/=",
            AssignmentOperator::Remainder => "%=",
            AssignmentOperator::Exponential => "**=",
            AssignmentOperator::ShiftLeft => "<<=",
            AssignmentOperator::ShiftRight => ">>=",
            AssignmentOperator::ShiftRightZeroFill => ">>=",
            AssignmentOperator::BitwiseAnd => "&=",
            AssignmentOperator::BitwiseOR => "|=",
            AssignmentOperator::BitwiseXOR => "^=",
            AssignmentOperator::LogicalAnd => "&&=",
            AssignmentOperator::LogicalOr => "||=",
            AssignmentOperator::LogicalNullish => "??=",
        }
    }

    pub(crate) fn logical_op(op: LogicalOperator) -> &'static str {
        match op {
            LogicalOperator::And => "and",
            LogicalOperator::Or => "or",
            // Zig uses `orelse` for null-coalescing, not `??`
            LogicalOperator::Coalesce => "orelse",
        }
    }

    pub(crate) fn unary_prefix(op: UnaryOperator) -> &'static str {
        match op {
            UnaryOperator::UnaryNegation => "-",
            UnaryOperator::UnaryPlus => "+",
            UnaryOperator::LogicalNot => "!",
            UnaryOperator::BitwiseNot => "~",
            _ => "",
        }
    }
}

// ── Output helpers ──────────────────────────────────

impl Codegen {
    pub fn write(&mut self, s: &str) {
        self.output.push_str(s);
    }

    pub fn push(&mut self, ch: char) {
        self.output.push(ch);
    }

    pub fn writeln(&mut self, s: &str) {
        self.write_indent();
        self.output.push_str(s);
        self.output.push('\n');
    }

    pub fn write_indent(&mut self) {
        for _ in 0..self.indent {
            self.output.push_str("    ");
        }
    }

    /// Generate a unique block label (blk_0, blk_1, ...) to avoid
    /// "redefinition of label" errors when multiple block expressions
    /// appear in the same scope.
    pub fn next_label(&mut self) -> String {
        let id = self.label_counter;
        self.label_counter += 1;
        format!("blk_{}", id)
    }

    /// Start a block expression with a unique label.
    /// Writes `(label: {` and returns the label string.
    /// The caller must use `emit_break()` with the same label.
    pub fn emit_blk_start(&mut self) -> String {
        let label = self.next_label();
        self.write(&format!("({}: {{", label));
        label
    }

    /// Emit `break :label expr` inside a block expression.
    pub fn emit_blk_break(&mut self, label: &str, expr: &str) {
        self.write(&format!("break :{} {}", label, expr));
    }

    /// End a block expression: writes `})`.
    pub fn emit_blk_end(&mut self) {
        self.write("}");
    }
}

// ── Argument extraction helpers ───────────────────

impl Codegen {
    /// Extract the first call argument as a string (via emit_expr_to_string).
    /// Returns empty string if no argument or argument is not an expression.
    pub(crate) fn first_arg_string(&mut self, args: &[Argument]) -> String {
        if let Some(arg) = args.first()
            && let Some(expr) = arg.as_expression()
        {
            self.emit_expr_to_string(expr)
        } else {
            String::new()
        }
    }

    /// Extract the first call argument as a string, with a custom fallback.
    pub(crate) fn first_arg_string_or(&mut self, args: &[Argument], default: &str) -> String {
        if let Some(arg) = args.first()
            && let Some(expr) = arg.as_expression()
        {
            self.emit_expr_to_string(expr)
        } else {
            default.to_string()
        }
    }

    /// Emit the first call argument directly. Silently does nothing if none.
    pub(crate) fn emit_first_arg(&mut self, args: &[Argument]) {
        if let Some(arg) = args.first()
            && let Some(expr) = arg.as_expression()
        {
            // Arguments are in expression position, never statement position.
            let saved = self.in_expr_stmt;
            self.in_expr_stmt = false;
            self.emit_expr(expr);
            self.in_expr_stmt = saved;
        }
    }

    /// Emit all call arguments separated by ", ".
    pub(crate) fn emit_comma_separated_args(&mut self, args: &[Argument]) {
        for (i, arg) in args.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            self.emit_expr_arg(arg);
        }
    }

    /// Emit array expression elements separated by ", ".
    /// Skips elements that are not expressions (spread, elision).
    pub(crate) fn emit_comma_separated_array_elements(
        &mut self,
        elements: &[ArrayExpressionElement],
    ) {
        for (i, elem) in elements.iter().enumerate() {
            if i > 0 {
                self.write(", ");
            }
            if let Some(e) = elem.as_expression() {
                self.emit_expr(e);
            }
        }
    }
}

// ── Template / format string helpers ───────────────

impl Codegen {
    /// If the callee is `obj.method()`, return `Some("obj")`.
    /// Returns `None` if it's not a static member expression on an identifier.
    pub(crate) fn callee_object_name<'a>(&self, callee: &'a Expression) -> Option<&'a str> {
        if let Expression::StaticMemberExpression(mem) = callee
            && let Expression::Identifier(obj) = &mem.object
        {
            Some(obj.name.as_str())
        } else {
            None
        }
    }

    /// Like `callee_object_name` but also handles string literals:
    /// `"hello".trimStart()` → `Some("\"hello\"")`.
    /// For identifiers, returns the name. For string literals, returns the escaped literal.
    /// Returns `None` for other object types.
    pub(crate) fn callee_object_repr(&self, callee: &Expression) -> Option<String> {
        if let Expression::StaticMemberExpression(mem) = callee {
            if let Expression::Identifier(obj) = &mem.object {
                return Some(obj.name.to_string());
            }
            if let Expression::StringLiteral(lit) = &mem.object {
                let escaped = lit
                    .value
                    .as_str()
                    .replace('\\', "\\\\")
                    .replace('\"', "\\\"")
                    .replace('\n', "\\n")
                    .replace('\r', "\\r")
                    .replace('\t', "\\t");
                return Some(format!("\"{}\"", escaped));
            }
        }
        None
    }

    /// Mutable version of `callee_object_repr` that can handle complex expressions.
    ///
    /// Falls back to `emit_expr_to_string()` for non-Identifier/non-StringLiteral objects,
    /// enabling method chaining like `encodeURI(str).replace(...)` or
    /// `new Date().getTime()`.
    pub(crate) fn callee_object_repr_mut(&mut self, callee: &Expression) -> Option<String> {
        // Fast path: try the immutable version first (Identifier, StringLiteral)
        if let Some(repr) = self.callee_object_repr(callee) {
            return Some(repr);
        }
        // Fallback: emit the object expression to a string for complex expressions
        if let Expression::StaticMemberExpression(mem) = callee {
            return Some(self.emit_expr_to_string(&mem.object));
        }
        None
    }

    /// Try to emit a numeric literal receiver for Number instance methods
    /// (toFixed, toExponential, toPrecision).
    /// Handles `(77.1234).method()` where the AST object is
    /// `ParenthesizedExpression(NumericLiteral)`.
    /// `args_required`: if true, the method requires at least 1 argument.
    /// Returns true if successfully emitted.
    pub(crate) fn emit_numeric_receiver(
        &mut self,
        callee: &Expression,
        method_name: &str,
        arguments: &[Argument],
        args_required: bool,
    ) -> bool {
        let num_expr = if let Expression::StaticMemberExpression(mem) = callee {
            match &mem.object {
                Expression::NumericLiteral(_) => Some(&mem.object),
                Expression::ParenthesizedExpression(pe)
                    if matches!(&pe.expression, Expression::NumericLiteral(_)) =>
                {
                    Some(&pe.expression)
                }
                _ => None,
            }
        } else {
            None
        };
        if let Some(expr) = num_expr {
            if args_required && arguments.is_empty() {
                self.errors
                    .push(format!("{method_name}() requires at least 1 argument"));
                return false;
            }
            self.write(&format!(
                "js_number.{method_name}(js_allocator.getAllocator(), ",
            ));
            self.emit_expr(expr);
            if arguments.is_empty() {
                self.write(", null");
            } else {
                self.write(", ");
                self.emit_first_arg(arguments);
            }
            self.write(")");
            return true;
        }
        false
    }

    /// Emit a format string expression: either a plain string literal (no args)
    /// or an allocPrint call (when interpolating args).
    ///
    /// `fmt` is a Zig format string (with `{{` / `}}` escaped).
    /// `args` are the already-emitted argument strings.
    pub(crate) fn emit_format_string(&mut self, fmt: &str, args: &[String]) {
        if args.is_empty() {
            // Pure-text → plain string literal (no allocation).
            self.write(&format!(
                "\"{}\"",
                fmt.replace("{{", "{").replace("}}", "}")
            ));
        } else {
            let args_str = format!(".{{{}}}", args.join(", "));
            self.write(&format!(
                "std.fmt.allocPrint(js_allocator.getAllocator(), \"{}\", {}) catch @panic(\"OOM: template literal allocPrint\")",
                fmt, args_str
            ));
        }
    }
}

// ── emit_toplevel helpers ──────────────────────────
