import inspect
import typing
import json
from pathlib import Path
from ..apputils.console import log, print_rule

import inspect
import typing
import json
import ast
from pathlib import Path
from ..apputils.console import log, print_rule

def python_type_to_ts(py_type):
    """Maps Python types (including typing hints) to TypeScript types."""
    if py_type == str: return "string"
    if py_type in (int, float): return "number"
    if py_type == bool: return "boolean"
    if py_type in (type(None), None): return "void"
    if py_type == list: return "any[]"
    if py_type == dict: return "Record<string, any>"
    
    # Check for Pydantic
    try:
        import pydantic
        if isinstance(py_type, type) and issubclass(py_type, pydantic.BaseModel):
            return "any" # For now, keep it simple
    except:
        pass

    # Handle typing module
    origin = getattr(py_type, '__origin__', None)
    args = getattr(py_type, '__args__', ())
    
    if origin in (list, typing.List):
        if args: return f"{python_type_to_ts(args[0])}[]"
        return "any[]"
    
    if origin in (dict, typing.Dict, typing.Mapping):
        if args and len(args) == 2:
            return f"Record<{python_type_to_ts(args[0])}, {python_type_to_ts(args[1])}>"
        return "Record<string, any>"
        
    if origin is typing.Union:
        non_none = [t for t in args if t not in (type(None), None)]
        if len(non_none) == 1:
            return f"{python_type_to_ts(non_none[0])} | null"
        return " | ".join([python_type_to_ts(t) for t in non_none])

    if origin in (typing.Optional,):
        return f"{python_type_to_ts(args[0])} | null"

    return "any"

def generate_ts_definitions(app_instance, output_path):
    """Generates TS definitions by inspecting a running App instance."""
    lines = [
        "/* Auto-generated by CactusCat */",
        "// Feel free to extend this file, but runtime changes will overwrite it.",
        "",
        "export interface CactusState {",
    ]
    
    # 1. State
    for key, value in app_instance.state.items():
        ts_type = python_type_to_ts(type(value))
        lines.append(f"  {key}: {ts_type};")
    lines.append("}")
    lines.append("")
    
    # 2. RPC Functions
    lines.append("export interface CactusAPI {")
    for name, func in app_instance.functions.items():
        try:
            sig = inspect.signature(func)
            params = []
            for p_name, param in sig.parameters.items():
                if p_name == "self": continue
                p_type = python_type_to_ts(param.annotation) if param.annotation != inspect.Parameter.empty else "any"
                params.append(f"{p_name}: {p_type}")
            
            ret_type = python_type_to_ts(sig.return_annotation) if sig.return_annotation != inspect.Parameter.empty else "any"
            lines.append(f"  {name}({', '.join(params)}): Promise<{ret_type}>;")
        except:
            lines.append(f"  {name}(...args: any[]): Promise<any>;")
            
    lines.append("}")
    lines.append("")
    lines.append("declare global {")
    lines.append("  const cactus: CactusAPI & { state: CactusState };")
    lines.append("}")
    lines.append("export default cactus;")

    content = "\n".join(lines)
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    Path(output_path).write_text(content, encoding="utf-8")
    log(f"TypeScript definitions generated at {output_path}", style="success")

def cmd_ts_gen(args):
    print_rule("CactusCat TS Generator")
    script_path = Path(args.script or "app.py")
    if not script_path.exists():
        log(f"Error: Script '{script_path}' not found.", style="error")
        return 1

    log("Scanning app for definition metadata (AST mode)...", style="info")
    
    try:
        tree = ast.parse(script_path.read_text(encoding="utf-8"))
        exposed_functions = []
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                for dec in node.decorator_list:
                    # Look for @app.expose, @expose, @app.rpc, @rpc
                    if (isinstance(dec, ast.Attribute) and dec.attr in ("expose", "rpc")) or \
                       (isinstance(dec, ast.Name) and dec.id in ("expose", "rpc")):
                        exposed_functions.append(node)
        
        output = args.output or "frontend/src/cactus.d.ts"
        log(f"Generating definitions for {len(exposed_functions)} functions to {output}...", style="dim")
        
        lines = [
            "/* Auto-generated by CactusCat */",
            "export interface CactusState {",
            "  [key: string]: any;",
            "}",
            "",
            "export interface CactusAPI {",
        ]
        
        for func in exposed_functions:
            params = []
            for arg in func.args.args:
                if arg.arg == "self": continue
                # Try to extract type hint if it's a simple Name
                hint = "any"
                if arg.annotation:
                    if isinstance(arg.annotation, ast.Name):
                        hint = arg.annotation.id
                    elif isinstance(arg.annotation, ast.Constant):
                        hint = str(arg.annotation.value)
                
                # Simplified mapping for common names
                ts_hint = "any"
                mapping = {"str": "string", "int": "number", "float": "number", "bool": "boolean", "None": "void"}
                ts_hint = mapping.get(hint, "any")
                
                params.append(f"{arg.arg}: {ts_hint}")
            
            lines.append(f"  {func.name}({', '.join(params)}): Promise<any>;")
            
        lines.extend([
            "}",
            "",
            "declare global {",
            "  const cactus: CactusAPI & { state: CactusState };",
            "}",
            "export default cactus;"
        ])

        Path(output).parent.mkdir(parents=True, exist_ok=True)
        Path(output).write_text("\n".join(lines), encoding="utf-8")
        log("✅ TS Generation complete.", style="success")
        return 0
    except Exception as e:
        log(f"Failed to generate TS: {e}", style="error")
        return 1
