"""
BlinkUI Transpiler — C Code Generator

Takes a typed ScreenDef and emits C code that:
1. Defines a struct for screen state
2. Generates build() as a BKNode* function
3. Generates event handlers as C functions
4. Generates a render request on state change
"""

import ast
import sys
sys.path.insert(0, ".")

from parser import ScreenDef, StateVar, EventHandler
from type_inferrer import TypeInferrer, infer_c_declaration, infer_c_initial

# ── Component → C function mapping ──

COMPONENT_MAP = {
    "VStack":      "bk_vstack",
    "HStack":      "bk_hstack",
    "ZStack":      "bk_zstack",
    "Text":        "bk_text",
    "Heading":     "bk_heading",
    "Label":       "bk_label",
    "Button":      "bk_button",
    "Card":        "bk_card",
    "ScrollView":  "bk_scroll",
    "Image":       "bk_image",
    "TextField":   "bk_textfield",
    "Toggle":      "bk_toggle",
    "Slider":      "bk_slider",
    "Spacer":      "bk_spacer",
    "Divider":     "bk_divider",
    "TextField":   "bk_textfield",
    "Toggle":      "bk_toggle",
    "Slider":      "bk_slider",
    "List":        "bk_scroll",
    "ListView":    "bk_scroll",
}

# ── Code Generator ──

class CCodeGenerator:

    def __init__(self):
        self.node_counter  = 0
        self.string_temps  = []  # temp char buffers needed
        self.indent_level  = 0

    def generate(self, screen: ScreenDef) -> str:
        lines = []

        # header
        lines.append(self._header(screen))

        # struct
        lines.append(self._gen_struct(screen))

        # init function
        lines.append(self._gen_init(screen))

        # build function
        lines.append(self._gen_build(screen))

        # event handlers
        for handler in screen.event_handlers:
            lines.append(self._gen_handler(handler, screen))

        # get_tree — called by Android bridge
        lines.append(self._gen_get_tree(screen))

        # on_event dispatcher
        lines.append(self._gen_on_event(screen))

        return "\n".join(lines)

    # ── Header ──

    def _header(self, screen: ScreenDef) -> str:
        return f'''// ─────────────────────────────────────────
// Generated by BlinkUI Transpiler
// Screen: {screen.name}
// DO NOT EDIT — regenerated on blink build
// ─────────────────────────────────────────

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "blinkui.h"
#include "reconciler.h"
#include "events.h"
#include "../animation/animation.h"

// Forward declarations
typedef struct _{screen.name} {screen.name};
BKNode* {screen.name}_build({screen.name}* self);
char*   {screen.name}_get_tree({screen.name}* self);
void    {screen.name}_on_event({screen.name}* self, int node_id, int event_type);
'''

    # ── Struct ──

    def _gen_struct(self, screen: ScreenDef) -> str:
        fields = []
        for sv in screen.state_vars:
            fields.append(f"    {infer_c_declaration(sv)};")
        fields_str = "\n".join(fields) if fields else "    int _dummy;"
        return f'''// ── State struct ──
struct _{screen.name} {{
{fields_str}
}};
'''

    # ── Init ──

    def _gen_init(self, screen: ScreenDef) -> str:
        inits = []
        for sv in screen.state_vars:
            if sv.inferred_type == "char":
                inits.append(
                    f'    strncpy(self->{sv.name}, {infer_c_initial(sv)}, 255);'
                )
            else:
                inits.append(
                    f'    self->{sv.name} = {infer_c_initial(sv)};'
                )
        inits_str = "\n".join(inits) if inits else "    // no state"
        return f'''// ── Init ──
{screen.name}* {screen.name}_init(void) {{
    {screen.name}* self = ({screen.name}*)malloc(sizeof({screen.name}));
{inits_str}
    return self;
}}
'''

    # ── Build ──

    def _gen_build(self, screen: ScreenDef) -> str:
        if not screen.build_tree:
            return f'''BKNode* {screen.name}_build({screen.name}* self) {{
    return bk_text("Empty screen");
}}
'''
        self.node_counter = 0
        self.string_temps = []

        # find the return statement in build()
        ret_node = None
        for stmt in screen.build_tree.body:
            if isinstance(stmt, ast.Return):
                ret_node = stmt.value
                break

        if not ret_node:
            return f'''BKNode* {screen.name}_build({screen.name}* self) {{
    return bk_text("No return in build");
}}
'''

        body_lines = []

        # generate string temps first (pass 1)
        self._collect_string_temps(ret_node, screen, body_lines)

        # generate component tree (pass 2)
        tree_expr = self._gen_component(ret_node, screen)

        body = "\n".join(f"    {l}" for l in body_lines)
        return f'''// ── Build ──
BKNode* {screen.name}_build({screen.name}* self) {{
{body}
    return {tree_expr};
}}
'''

    def _collect_string_temps(self, node, screen, lines):
        """Pre-generate char buffers for formatted strings."""
        if isinstance(node, ast.Call):
            func_name = self._get_func_name(node)
            if func_name in COMPONENT_MAP:
                inner = self._unwrap_component_call(node)
                for arg in inner.args:
                    if isinstance(arg, ast.JoinedStr):  # f-string
                        temp_name = f"_str_{self.node_counter}"
                        self.node_counter += 1
                        fmt, vars_ = self._parse_fstring(arg, screen)
                        if vars_:
                            vars_str = ", ".join(vars_)
                            lines.append(
                                f'char {temp_name}[512]; '
                                f'snprintf({temp_name}, 512, "{fmt}", {vars_str});'
                            )
                        else:
                            lines.append(
                                f'char {temp_name}[512]; '
                                f'snprintf({temp_name}, 512, "{fmt}");'
                            )
                        # store mapping
                        self.string_temps.append((id(arg), temp_name))
                # recurse into children (keyword args like children=[...])
                for kw in node.keywords:
                    if isinstance(kw.value, ast.List):
                        for elt in kw.value.elts:
                            self._collect_string_temps(elt, screen, lines)
                for arg in node.args:
                    if isinstance(arg, ast.Call):
                        self._collect_string_temps(arg, screen, lines)

    def _gen_component(self, node, screen, depth=0) -> str:
        if not isinstance(node, ast.Call):
            return "NULL"

        func_name = self._get_func_name(node)
        c_func    = COMPONENT_MAP.get(func_name)

        if not c_func:
            return "NULL"

        self.node_counter += 1
        node_id = self.node_counter

        # unwrap method chain to get actual component call with its args
        inner = self._unwrap_component_call(node)

        # get first arg (label/content) from inner call
        content = "NULL"
        if inner.args:
            content = self._gen_string_arg(inner.args[0], screen)

        # get children from chained calls or positional args
        children = self._get_children(node, screen, depth)

        # get props from method chain
        props = self._get_props(node)

        # layout components take children
        if func_name in ("VStack", "HStack", "ZStack", "ScrollView", "Card"):
            children_str = self._gen_children_array(children, screen, depth)
            return (f'{c_func}('
                    f'{children_str}, '
                    f'{len(children)}, '
                    f'{node_id})')

        # Button takes label + node_id
        if func_name == "Button":
            return f'{c_func}({content}, {node_id})'

        # Text/Heading/Label take content
        return f'{c_func}({content}, {node_id})'

    def _get_children(self, node, screen, depth):
        children = []
        inner = self._unwrap_component_call(node)
        # positional args that are component calls
        for arg in inner.args:
            if isinstance(arg, ast.Call):
                fname = self._get_func_name(arg)
                if fname in COMPONENT_MAP:
                    children.append(arg)
        # keyword children=[]
        for kw in inner.keywords:
            if kw.arg == "children" and isinstance(kw.value, ast.List):
                for elt in kw.value.elts:
                    if isinstance(elt, ast.Call):
                        children.append(elt)
        return children

    def _gen_children_array(self, children, screen, depth) -> str:
        if not children:
            return "NULL"
        child_exprs = [self._gen_component(c, screen, depth+1) for c in children]
        arr = ", ".join(child_exprs)
        return f"(BKNode*[]){{{arr}}}"

    def _get_props(self, node) -> dict:
        props = {}
        # walk method chain: Button("x").on_tap(self.fn).color("#fff")
        current = node
        while isinstance(current, ast.Call):
            if isinstance(current.func, ast.Attribute):
                method = current.func.attr
                if method == "on_tap" and current.args:
                    arg = current.args[0]
                    if isinstance(arg, ast.Attribute):
                        props["on_tap"] = arg.attr
                current = current.func.value
            else:
                break
        return props

    def _gen_string_arg(self, node, screen) -> str:
        if isinstance(node, ast.Constant):
            return f'"{node.value}"'
        if isinstance(node, ast.JoinedStr):
            # find pre-generated temp
            for (nid, temp_name) in self.string_temps:
                if nid == id(node):
                    return temp_name
            # fallback
            return '"..."'
        return '"..."'

    def _parse_fstring(self, node: ast.JoinedStr, screen) -> tuple:
        fmt_parts = []
        vars_     = []
        for part in node.values:
            if isinstance(part, ast.Constant):
                fmt_parts.append(str(part.value).replace("%", "%%"))
            elif isinstance(part, ast.FormattedValue):
                val = part.value
                if (isinstance(val, ast.Attribute) and
                    isinstance(val.value, ast.Name) and
                    val.value.id == "self"):
                    sv = self._find_state_var(screen, val.attr)
                    if sv:
                        if sv.inferred_type == "int":
                            fmt_parts.append("%d")
                            vars_.append(f"self->{val.attr}")
                        elif sv.inferred_type == "float":
                            fmt_parts.append("%.2f")
                            vars_.append(f"self->{val.attr}")
                        elif sv.inferred_type == "char":
                            fmt_parts.append("%s")
                            vars_.append(f"self->{val.attr}")
                        else:
                            fmt_parts.append("%d")
                            vars_.append(f"self->{val.attr}")
                    else:
                        fmt_parts.append("%s")
                        vars_.append('"?"')
                else:
                    fmt_parts.append("%s")
                    vars_.append('"?"')
        return "".join(fmt_parts), vars_

    def _find_state_var(self, screen, name) -> StateVar:
        for sv in screen.state_vars:
            if sv.name == name:
                return sv
        return None

    def _get_func_name(self, node) -> str:
        if isinstance(node, ast.Call):
            func = node.func
            while True:
                if isinstance(func, ast.Name):
                    return func.id
                if isinstance(func, ast.Attribute):
                    if isinstance(func.value, ast.Call):
                        func = func.value.func
                    else:
                        return func.attr
                else:
                    break
        return ""

    def _unwrap_component_call(self, node) -> ast.Call:
        """Unwrap Button(x).on_tap(y) to get the inner Button(x) call."""
        current = node
        while isinstance(current, ast.Call):
            if isinstance(current.func, ast.Attribute):
                inner = current.func.value
                fname = self._get_func_name(inner)
                if fname in COMPONENT_MAP:
                    return inner
                current = inner
            else:
                break
        return node

    # ── Event handlers ──

    def _gen_handler(self, handler: EventHandler, screen: ScreenDef) -> str:
        body_lines = []
        for stmt in handler.body:
            line = self._gen_stmt(stmt, screen)
            if line:
                body_lines.append(f"    {line}")

        # always re-render after handler
        body_lines.append(f"    // re-render")
        body_lines.append(f"    bk_request_render(self);")

        body = "\n".join(body_lines)
        return f'''// ── Handler: {handler.name} ──
void {screen.name}_{handler.name}({screen.name}* self) {{
{body}
}}
'''

    def _gen_stmt(self, stmt, screen) -> str:
        # self.navigate/fetch/post → C calls
        if isinstance(stmt, ast.Expr):
            val = stmt.value
            if isinstance(val, ast.Call):
                if isinstance(val.func, ast.Attribute):
                    if val.func.attr == "navigate" and val.args:
                        if isinstance(val.args[0], ast.Constant):
                            screen = val.args[0].value
                            return 'extern void bk_navigate(int screen_index); bk_navigate(1);'
                    if val.func.attr == "pop":
                        return "extern void bk_navigate(int screen_index); bk_navigate(0);"
                    if val.func.attr == "fetch" and val.args:
                        if isinstance(val.args[0], ast.Constant):
                            url = val.args[0].value
                            rid = abs(hash(url)) % 9999
                            return ('extern void bk_http_get(const char* url, int rid, void* s); '
                                    'bk_http_get("' + url + '", ' + str(rid) + ', self);')
                    if val.func.attr == "post" and val.args:
                        if isinstance(val.args[0], ast.Constant):
                            url = val.args[0].value
                            rid = abs(hash(url)) % 9999
                            return ('extern void bk_http_post(const char* url, const char* body, int rid, void* s); '
                                    'bk_http_post("' + url + '", "{}", ' + str(rid) + ', self);')

        # self.count += 1
        if isinstance(stmt, ast.AugAssign):
            target = stmt.target
            if (isinstance(target, ast.Attribute) and
                isinstance(target.value, ast.Name) and
                target.value.id == "self"):
                op  = self._gen_op(stmt.op)
                val = self._gen_expr(stmt.value, screen)
                return f"self->{target.attr} {op}= {val};"

        # self.count = 0
        if isinstance(stmt, ast.Assign):
            if (len(stmt.targets) == 1 and
                isinstance(stmt.targets[0], ast.Attribute) and
                isinstance(stmt.targets[0].value, ast.Name) and
                stmt.targets[0].value.id == "self"):
                attr = stmt.targets[0].attr
                sv   = self._find_state_var(screen, attr)
                val  = self._gen_expr(stmt.value, screen)
                if sv and sv.inferred_type == "char":
                    return f'strncpy(self->{attr}, {val}, 255);'
                return f"self->{attr} = {val};"

        # self._navigator.pop() → bk_navigate(0)
        if isinstance(stmt, ast.Expr):
            val = stmt.value
            if isinstance(val, ast.Call):
                if isinstance(val.func, ast.Attribute):
                    if val.func.attr == "pop":
                        return "extern void bk_navigate(int screen_index); bk_navigate(0);"
                    if val.func.attr == "navigate" and val.args:
                        if isinstance(val.args[0], ast.Constant):
                            screen = val.args[0].value
                            return f'extern void bk_navigate(int screen_index); bk_navigate(1); /* {screen} */'

        # if self.count > 0:
        if isinstance(stmt, ast.If):
            test  = self._gen_expr(stmt.test, screen)
            body  = [self._gen_stmt(s, screen) for s in stmt.body]
            body  = "\n".join(f"        {l}" for l in body if l)
            return f"if ({test}) {{\n{body}\n    }}"

        return None

    def _gen_expr(self, node, screen) -> str:
        if isinstance(node, ast.Constant):
            if isinstance(node.value, str):
                return f'"{node.value}"'
            if isinstance(node.value, bool):
                return "1" if node.value else "0"
            return str(node.value)

        if isinstance(node, ast.Attribute):
            if (isinstance(node.value, ast.Name) and
                node.value.id == "self"):
                return f"self->{node.attr}"

        if isinstance(node, ast.BinOp):
            left  = self._gen_expr(node.left,  screen)
            right = self._gen_expr(node.right, screen)
            op    = self._gen_binop(node.op)
            return f"({left} {op} {right})"

        if isinstance(node, ast.Compare):
            left = self._gen_expr(node.left, screen)
            if node.ops and node.comparators:
                op    = self._gen_cmpop(node.ops[0])
                right = self._gen_expr(node.comparators[0], screen)
                return f"({left} {op} {right})"

        if isinstance(node, ast.UnaryOp):
            if isinstance(node.op, ast.Not):
                val = self._gen_expr(node.operand, screen)
                return f"!({val})"
            if isinstance(node.op, ast.USub):
                val = self._gen_expr(node.operand, screen)
                return f"-{val}"

        return "0"

    def _gen_op(self, op) -> str:
        ops = {
            ast.Add: "+", ast.Sub: "-",
            ast.Mult: "*", ast.Div: "/"
        }
        return ops.get(type(op), "+")

    def _gen_binop(self, op) -> str:
        return self._gen_op(op)

    def _gen_cmpop(self, op) -> str:
        ops = {
            ast.Gt: ">", ast.Lt: "<",
            ast.GtE: ">=", ast.LtE: "<=",
            ast.Eq: "==", ast.NotEq: "!="
        }
        return ops.get(type(op), "==")

    # ── get_tree — serialize build() result to JSON ──

    def _gen_get_tree(self, screen: ScreenDef) -> str:
        return f'''// ── get_tree: called by Android bridge ──
char* {screen.name}_get_tree({screen.name}* self) {{
    // TODO: serialize BKNode tree to JSON
    // For now: inline JSON generation
    static char buf[8192];
    {self._gen_inline_json(screen)}
    return buf;
}}
'''

    def _gen_inline_json(self, screen: ScreenDef) -> str:
        # generate inline snprintf for the tree
        # this is a simplified version — full version uses node serializer
        state_displays = []
        for sv in screen.state_vars:
            if sv.inferred_type == "int":
                state_displays.append(f"self->{sv.name}")
            elif sv.inferred_type == "char":
                state_displays.append(f"self->{sv.name}")

        return f'snprintf(buf, 8192, "{{\\\"type\\\":\\\"VStack\\\"}}", 0); // placeholder'

    # ── on_event dispatcher ──

    def _gen_on_event(self, screen: ScreenDef) -> str:
        # map node_ids to handler names from on_tap props
        # for now generate a dispatcher that calls all handlers
        cases = []
        node_id = 42
        for handler in screen.event_handlers:
            cases.append(
                f'    if (node_id == {node_id} && event_type == 0) '
                f'{screen.name}_{handler.name}(self);'
            )
            node_id += 1

        cases_str = "\n".join(cases) if cases else "    // no handlers"
        return f'''// ── on_event dispatcher ──
void {screen.name}_on_event({screen.name}* self, int node_id, int event_type) {{
{cases_str}
}}
'''


# ── Test ──

if __name__ == "__main__":
    from parser import BlinkUIParser

    test_source = '''
from blinkui import Screen, state
from blinkui.components import VStack, Text, Button

class HomeScreen(Screen):
    count = state(0)
    name  = state("John")

    def build(self):
        return VStack(
            Text(f"Count: {self.count}"),
            Text(f"Hello {self.name}"),
            Button("Tap Me").on_tap(self.increment),
            Button("Reset").on_tap(self.reset),
        )

    def increment(self):
        self.count += 1

    def reset(self):
        self.count = 0
        self.name  = "Reset"
'''

    parser   = BlinkUIParser()
    screen   = parser.parse_source(test_source)
    inferrer = TypeInferrer()
    screen   = inferrer.infer(screen)
    gen      = CCodeGenerator()
    c_code   = gen.generate(screen)
    print(c_code)
