"""C++ code generator for Core IL.

This file implements Core IL v1.6 to C++17 transpilation.
Core IL v1.6 adds OOP-style method calls and property access (Tier 2).

The generated C++ code:
- Matches interpreter semantics exactly
- Requires C++17 (std::variant, std::optional)
- Uses coreil_runtime.hpp for runtime support
- Preserves map insertion order
- Implements short-circuit evaluation
- Record support (mutable named fields)
- Set operations (membership, add, remove, size)
- Deque operations (double-ended queue)
- Heap operations (min-heap priority queue)
- Math operations (sin, cos, tan, sqrt, floor, ceil, abs, log, exp, pow, pi, e)
- JSON operations (requires nlohmann/json.hpp)
- Regex operations (uses <regex>)
- Array slicing (Slice)
- Unary not (Not)
- OOP-style method calls and property access (Tier 2)

Version history:
- v1.6: Added MethodCall and PropertyGet for OOP-style APIs (Tier 2, non-portable)
- v1.5: Initial C++ backend

Backward compatibility: Accepts v0.1 through v1.6 programs.
"""

from __future__ import annotations

from pathlib import Path

from english_compiler.coreil.emit_base import BaseEmitter


class CppEmitter(BaseEmitter):
    """C++ code emitter for Core IL."""

    @property
    def indent_str(self) -> str:
        return "    "

    def _setup_state(self) -> None:
        """Initialize C++-specific state."""
        self.uses_json = False
        self.uses_regex = False
        self.external_modules: set[str] = set()

    def emit(self) -> str:
        """Generate C++ code from Core IL document."""
        # Separate function definitions from main body
        body = self.doc.get("body", [])
        func_defs = [stmt for stmt in body if stmt.get("type") == "FuncDef"]
        main_stmts = [stmt for stmt in body if stmt.get("type") != "FuncDef"]

        # Generate function definitions
        for stmt in func_defs:
            self.emit_stmt(stmt)
            self.emit_line("")

        # Generate main function
        self.emit_line("int main() {")
        self.indent_level = 1
        for stmt in main_stmts:
            self.emit_stmt(stmt)
        self.emit_line("return 0;")
        self.indent_level = 0
        self.emit_line("}")

        return self._build_output()

    def _build_output(self) -> str:
        """Build final output with headers."""
        header_lines = [
            "// Generated by English Compiler - Core IL to C++ transpiler",
            "// Requires: C++17 compiler (g++ -std=c++17, clang++ -std=c++17)",
            "",
        ]

        # Add JSON include if needed (before runtime header)
        if self.uses_json:
            header_lines.append('#include "json.hpp"')

        # Add runtime header include
        header_lines.append('#include "coreil_runtime.hpp"')
        header_lines.append("")

        # Add non-portable warning if external modules used
        if self.external_modules:
            header_lines.insert(0, "// WARNING: This program uses external calls and is NOT PORTABLE")
            header_lines.insert(1, f"// Required external modules: {', '.join(sorted(self.external_modules))}")
            header_lines.insert(2, "")

        return "\n".join(header_lines + self.lines) + "\n"

    # ========== Expression Handlers ==========

    def _emit_literal(self, node: dict) -> str:
        value = node.get("value")
        if isinstance(value, str):
            escaped = self.escape_string(value)
            return f'coreil::Value(std::string("{escaped}"))'
        elif isinstance(value, bool):
            return "coreil::Value(true)" if value else "coreil::Value(false)"
        elif value is None:
            return "coreil::Value(nullptr)"
        elif isinstance(value, int):
            return f"coreil::Value(static_cast<int64_t>({value}))"
        elif isinstance(value, float):
            return f"coreil::Value({value})"
        else:
            return f"coreil::Value({value})"

    def _emit_binary(self, node: dict) -> str:
        op = node.get("op")
        left = self.emit_expr(node.get("left"))
        right = self.emit_expr(node.get("right"))

        op_map = {
            "+": "add",
            "-": "subtract",
            "*": "multiply",
            "/": "divide",
            "%": "modulo",
        }

        if op in op_map:
            return f"coreil::{op_map[op]}({left}, {right})"
        elif op == "==":
            return f"coreil::Value(coreil::equal({left}, {right}))"
        elif op == "!=":
            return f"coreil::Value(!coreil::equal({left}, {right}))"
        elif op == "<":
            return f"coreil::Value(coreil::less_than({left}, {right}))"
        elif op == "<=":
            return f"coreil::Value(coreil::less_than_or_equal({left}, {right}))"
        elif op == ">":
            return f"coreil::Value(coreil::greater_than({left}, {right}))"
        elif op == ">=":
            return f"coreil::Value(coreil::greater_than_or_equal({left}, {right}))"
        elif op == "and":
            return f"(coreil::is_truthy({left}) ? coreil::Value(coreil::is_truthy({right})) : coreil::Value(false))"
        elif op == "or":
            return f"(coreil::is_truthy({left}) ? coreil::Value(true) : coreil::Value(coreil::is_truthy({right})))"
        else:
            raise ValueError(f"unknown binary operator: {op}")

    def _emit_array(self, node: dict) -> str:
        items = node.get("items", [])
        if not items:
            return "coreil::make_array({})"
        item_strs = [self.emit_expr(item) for item in items]
        return "coreil::make_array({" + ", ".join(item_strs) + "})"

    def _emit_index(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        index = self.emit_expr(node.get("index"))
        return f"coreil::array_index({base}, {index})"

    def _emit_slice(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        start = self.emit_expr(node.get("start"))
        end = self.emit_expr(node.get("end"))
        return f"coreil::array_slice({base}, {start}, {end})"

    def _emit_not(self, node: dict) -> str:
        arg = self.emit_expr(node.get("arg"))
        return f"coreil::logical_not({arg})"

    def _emit_length(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::array_length({base})"

    def _emit_call_expr(self, node: dict) -> str:
        name = node.get("name")
        args = node.get("args", [])

        if name == "get_or_default":
            if len(args) != 3:
                raise ValueError(f"get_or_default expects 3 arguments, got {len(args)}")
            d = self.emit_expr(args[0])
            k = self.emit_expr(args[1])
            default = self.emit_expr(args[2])
            return f"coreil::map_get_default({d}, {k}, {default})"

        if name == "entries":
            if len(args) != 1:
                raise ValueError(f"entries expects 1 argument, got {len(args)}")
            d = self.emit_expr(args[0])
            return f"coreil::map_keys({d})"

        if name == "append":
            if len(args) != 2:
                raise ValueError(f"append expects 2 arguments, got {len(args)}")
            lst = self.emit_expr(args[0])
            value = self.emit_expr(args[1])
            return f"(coreil::array_push({lst}, {value}), coreil::Value(nullptr))"

        arg_strs = [self.emit_expr(arg) for arg in args]
        return f"{name}({', '.join(arg_strs)})"

    def _emit_map(self, node: dict) -> str:
        items = node.get("items", [])
        if not items:
            return "coreil::make_map({})"
        pairs = []
        for item in items:
            key_node = item.get("key")
            value_node = item.get("value")
            key = self.emit_expr(key_node)
            value = self.emit_expr(value_node)
            pairs.append(f"{{{key}, {value}}}")
        return "coreil::make_map({" + ", ".join(pairs) + "})"

    def _emit_get(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        key = self.emit_expr(node.get("key"))
        return f"coreil::map_get({base}, {key})"

    def _emit_get_default(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        key = self.emit_expr(node.get("key"))
        default = self.emit_expr(node.get("default"))
        return f"coreil::map_get_default({base}, {key}, {default})"

    def _emit_keys(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::map_keys({base})"

    def _emit_tuple(self, node: dict) -> str:
        items = node.get("items", [])
        item_strs = [self.emit_expr(item) for item in items]
        return "coreil::make_tuple({" + ", ".join(item_strs) + "})"

    def _emit_record(self, node: dict) -> str:
        fields = node.get("fields", [])
        if not fields:
            return "coreil::make_record({})"
        field_strs = []
        for field in fields:
            name = field.get("name")
            value = self.emit_expr(field.get("value"))
            field_strs.append(f'{{"{name}", {value}}}')
        return "coreil::make_record({" + ", ".join(field_strs) + "})"

    def _emit_get_field(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        name = node.get("name")
        return f'coreil::record_get_field({base}, "{name}")'

    def _emit_string_length(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::string_length({base})"

    def _emit_substring(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        start = self.emit_expr(node.get("start"))
        end = self.emit_expr(node.get("end"))
        return f"coreil::string_substring({base}, {start}, {end})"

    def _emit_char_at(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        index = self.emit_expr(node.get("index"))
        return f"coreil::string_char_at({base}, {index})"

    def _emit_join(self, node: dict) -> str:
        sep = self.emit_expr(node.get("sep"))
        items = self.emit_expr(node.get("items"))
        return f"coreil::string_join({sep}, {items})"

    def _emit_string_split(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        delimiter = self.emit_expr(node.get("delimiter"))
        return f"coreil::string_split({base}, {delimiter})"

    def _emit_string_trim(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::string_trim({base})"

    def _emit_string_upper(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::string_upper({base})"

    def _emit_string_lower(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::string_lower({base})"

    def _emit_string_starts_with(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        prefix = self.emit_expr(node.get("prefix"))
        return f"coreil::string_starts_with({base}, {prefix})"

    def _emit_string_ends_with(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        suffix = self.emit_expr(node.get("suffix"))
        return f"coreil::string_ends_with({base}, {suffix})"

    def _emit_string_contains(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        substring = self.emit_expr(node.get("substring"))
        return f"coreil::string_contains({base}, {substring})"

    def _emit_string_replace(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        old = self.emit_expr(node.get("old"))
        new = self.emit_expr(node.get("new"))
        return f"coreil::string_replace({base}, {old}, {new})"

    def _emit_set_expr(self, node: dict) -> str:
        items = node.get("items", [])
        if not items:
            return "coreil::make_set({})"
        item_strs = [self.emit_expr(item) for item in items]
        return "coreil::make_set({" + ", ".join(item_strs) + "})"

    def _emit_set_has(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        return f"coreil::set_has({base}, {value})"

    def _emit_set_size(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::set_size({base})"

    def _emit_deque_new(self, node: dict) -> str:
        return "coreil::deque_new()"

    def _emit_deque_size(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::deque_size({base})"

    def _emit_heap_new(self, node: dict) -> str:
        return "coreil::heap_new()"

    def _emit_heap_size(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::heap_size({base})"

    def _emit_heap_peek(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        return f"coreil::heap_peek({base})"

    def _emit_math(self, node: dict) -> str:
        op = node.get("op")
        arg = self.emit_expr(node.get("arg"))
        math_funcs = {
            "sin": "math_sin",
            "cos": "math_cos",
            "tan": "math_tan",
            "sqrt": "math_sqrt",
            "floor": "math_floor",
            "ceil": "math_ceil",
            "abs": "math_abs",
            "log": "math_log",
            "exp": "math_exp",
        }
        if op not in math_funcs:
            raise ValueError(f"unknown math operation: {op}")
        return f"coreil::{math_funcs[op]}({arg})"

    def _emit_math_pow(self, node: dict) -> str:
        base = self.emit_expr(node.get("base"))
        exponent = self.emit_expr(node.get("exponent"))
        return f"coreil::math_pow({base}, {exponent})"

    def _emit_math_const(self, node: dict) -> str:
        name = node.get("name")
        if name == "pi":
            return "coreil::math_pi()"
        elif name == "e":
            return "coreil::math_e()"
        raise ValueError(f"unknown math constant: {name}")

    def _emit_json_parse(self, node: dict) -> str:
        self.uses_json = True
        source = self.emit_expr(node.get("source"))
        return f"coreil::json_parse({source})"

    def _emit_json_stringify(self, node: dict) -> str:
        self.uses_json = True
        value = self.emit_expr(node.get("value"))
        pretty = node.get("pretty")
        if pretty:
            pretty_expr = self.emit_expr(pretty)
            return f"coreil::json_stringify({value}, coreil::is_truthy({pretty_expr}))"
        return f"coreil::json_stringify({value})"

    def _emit_regex_match(self, node: dict) -> str:
        self.uses_regex = True
        string = self.emit_expr(node.get("string"))
        pattern = self.emit_expr(node.get("pattern"))
        flags_node = node.get("flags")
        if flags_node:
            flags = self.emit_expr(flags_node)
            return f"coreil::regex_match({string}, {pattern}, std::get<std::string>({flags}))"
        return f"coreil::regex_match({string}, {pattern})"

    def _emit_regex_find_all(self, node: dict) -> str:
        self.uses_regex = True
        string = self.emit_expr(node.get("string"))
        pattern = self.emit_expr(node.get("pattern"))
        flags_node = node.get("flags")
        if flags_node:
            flags = self.emit_expr(flags_node)
            return f"coreil::regex_find_all({string}, {pattern}, std::get<std::string>({flags}))"
        return f"coreil::regex_find_all({string}, {pattern})"

    def _emit_regex_replace(self, node: dict) -> str:
        self.uses_regex = True
        string = self.emit_expr(node.get("string"))
        pattern = self.emit_expr(node.get("pattern"))
        replacement = self.emit_expr(node.get("replacement"))
        flags_node = node.get("flags")
        if flags_node:
            flags = self.emit_expr(flags_node)
            return f"coreil::regex_replace({string}, {pattern}, {replacement}, std::get<std::string>({flags}))"
        return f"coreil::regex_replace({string}, {pattern}, {replacement})"

    def _emit_regex_split(self, node: dict) -> str:
        self.uses_regex = True
        string = self.emit_expr(node.get("string"))
        pattern = self.emit_expr(node.get("pattern"))
        flags_node = node.get("flags")
        if flags_node:
            flags = self.emit_expr(flags_node)
            return f"coreil::regex_split({string}, {pattern}, std::get<std::string>({flags}))"
        return f"coreil::regex_split({string}, {pattern})"

    def _emit_external_call(self, node: dict) -> str:
        module = node.get("module")
        function = node.get("function")
        self.external_modules.add(module)
        raise ValueError(
            f"ExternalCall to {module}.{function} is not supported in C++ backend. "
            f"External calls require platform-specific implementation."
        )

    def _emit_method_call(self, node: dict) -> str:
        obj = self.emit_expr(node.get("object"))
        method = node.get("method")
        args = node.get("args", [])
        arg_strs = [self.emit_expr(arg) for arg in args]
        return f"{obj}.{method}({', '.join(arg_strs)})"

    def _emit_property_get(self, node: dict) -> str:
        obj = self.emit_expr(node.get("object"))
        prop = node.get("property")
        return f"{obj}.{prop}"

    # ========== Statement Handlers ==========

    def _emit_let(self, node: dict) -> None:
        name = node.get("name")
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::Value {name} = {value};")

    def _emit_assign(self, node: dict) -> None:
        name = node.get("name")
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"{name} = {value};")

    def _emit_if(self, node: dict) -> None:
        test = self.emit_expr(node.get("test"))
        self.emit_line(f"if (coreil::is_truthy({test})) {{")
        self.indent_level += 1
        then_body = node.get("then", [])
        if not then_body:
            self.emit_line("// empty")
        else:
            for stmt in then_body:
                self.emit_stmt(stmt)
        self.indent_level -= 1

        else_body = node.get("else")
        if else_body:
            self.emit_line("} else {")
            self.indent_level += 1
            for stmt in else_body:
                self.emit_stmt(stmt)
            self.indent_level -= 1
        self.emit_line("}")

    def _emit_while(self, node: dict) -> None:
        test = self.emit_expr(node.get("test"))
        self.emit_line(f"while (coreil::is_truthy({test})) {{")
        self.indent_level += 1
        body = node.get("body", [])
        if not body:
            self.emit_line("// empty")
        else:
            for stmt in body:
                self.emit_stmt(stmt)
        self.indent_level -= 1
        self.emit_line("}")

    def _emit_print(self, node: dict) -> None:
        args = node.get("args", [])
        arg_strs = [self.emit_expr(arg) for arg in args]
        self.emit_line(f"coreil::print({{{', '.join(arg_strs)}}});")

    def _emit_set_index(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        index = self.emit_expr(node.get("index"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::array_set_index({base}, {index}, {value});")

    def _emit_set_stmt(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        key = self.emit_expr(node.get("key"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::map_set({base}, {key}, {value});")

    def _emit_push(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::array_push({base}, {value});")

    def _emit_set_field(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        name = node.get("name")
        value = self.emit_expr(node.get("value"))
        self.emit_line(f'coreil::record_set_field({base}, "{name}", {value});')

    def _emit_set_add(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::set_add({base}, {value});")

    def _emit_set_remove(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::set_remove({base}, {value});")

    def _emit_push_back(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::deque_push_back({base}, {value});")

    def _emit_push_front(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::deque_push_front({base}, {value});")

    def _emit_pop_front(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        target = node.get("target")
        self.emit_line(f"coreil::Value {target} = coreil::deque_pop_front({base});")

    def _emit_pop_back(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        target = node.get("target")
        self.emit_line(f"coreil::Value {target} = coreil::deque_pop_back({base});")

    def _emit_heap_push(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        priority = self.emit_expr(node.get("priority"))
        value = self.emit_expr(node.get("value"))
        self.emit_line(f"coreil::heap_push({base}, {priority}, {value});")

    def _emit_heap_pop(self, node: dict) -> None:
        base = self.emit_expr(node.get("base"))
        target = node.get("target")
        self.emit_line(f"coreil::Value {target} = coreil::heap_pop({base});")

    def _emit_func_def(self, node: dict) -> None:
        name = node.get("name")
        params = node.get("params", [])
        param_strs = [f"coreil::Value {p}" for p in params]
        self.emit_line(f"coreil::Value {name}({', '.join(param_strs)}) {{")
        self.indent_level += 1
        body = node.get("body", [])
        if not body:
            self.emit_line("return coreil::Value(nullptr);")
        else:
            for stmt in body:
                self.emit_stmt(stmt)
            # Add implicit return if no explicit return
            if body and body[-1].get("type") != "Return":
                self.emit_line("return coreil::Value(nullptr);")
        self.indent_level -= 1
        self.emit_line("}")

    def _emit_return(self, node: dict) -> None:
        value = node.get("value")
        if value is None:
            self.emit_line("return coreil::Value(nullptr);")
        else:
            self.emit_line(f"return {self.emit_expr(value)};")

    def _emit_call_stmt(self, node: dict) -> None:
        name = node.get("name")
        args = node.get("args", [])

        if name == "append":
            if len(args) != 2:
                raise ValueError(f"append expects 2 arguments, got {len(args)}")
            lst = self.emit_expr(args[0])
            value = self.emit_expr(args[1])
            self.emit_line(f"coreil::array_push({lst}, {value});")
            return

        arg_strs = [self.emit_expr(arg) for arg in args]
        self.emit_line(f"{name}({', '.join(arg_strs)});")


def emit_cpp(doc: dict) -> str:
    """Generate C++ code from Core IL document.

    Returns C++ source code as a string.
    """
    emitter = CppEmitter(doc)
    return emitter.emit()



def get_runtime_header_path() -> Path:
    """Return the path to the coreil_runtime.hpp header file."""
    return Path(__file__).parent / "cpp_runtime" / "coreil_runtime.hpp"


def get_json_header_path() -> Path:
    """Return the path to the json.hpp header file."""
    return Path(__file__).parent / "cpp_runtime" / "json.hpp"

