import ast 
import inspect 
from dataclasses import dataclass 
from typing import Any ,Sequence 


@dataclass 
class Parameter :
    name :str 
    default :Any =inspect ._empty 
    kind :inspect ._ParameterKind =inspect .Parameter .POSITIONAL_OR_KEYWORD 


def get_signature_from_ast (func_def :ast .FunctionDef )->list [Parameter ]:
    """
    Extract parameter information from AST FunctionDef node

    Args:
        func_def: AST node of function definition

    Returns:
        List of Parameter objects describing the function signature
    """
    params =[]
    positional_count =len (func_def .args .args )
    default_count =len (func_def .args .defaults )
    base_count =positional_count -default_count 


    for i ,arg in enumerate (func_def .args .args ):
        default_index =i -base_count 
        if default_index <0 :
            param_default =inspect ._empty 
        else :
            default_node =func_def .args .defaults [default_index ]
            assert default_node is not None ,"Expected a non-None default_node"
            param_default =ast .literal_eval (default_node )

        params .append (
        Parameter (
        name =arg .arg ,
        default =param_default ,
        kind =inspect .Parameter .POSITIONAL_OR_KEYWORD ,
        )
        )


    if func_def .args .vararg :
        params .append (
        Parameter (
        name =func_def .args .vararg .arg ,
        kind =inspect .Parameter .VAR_POSITIONAL ,
        )
        )


    for arg ,kwonly_default_node in zip (
    func_def .args .kwonlyargs ,func_def .args .kw_defaults 
    ):
        if kwonly_default_node is None :
            param_default =inspect ._empty 
        else :
            param_default =ast .literal_eval (kwonly_default_node )

        params .append (
        Parameter (
        name =arg .arg ,
        default =param_default ,
        kind =inspect .Parameter .KEYWORD_ONLY ,
        )
        )


    if func_def .args .kwarg :
        params .append (
        Parameter (
        name =func_def .args .kwarg .arg ,
        kind =inspect .Parameter .VAR_KEYWORD ,
        )
        )

    return params 


def resolve_call_arguments (
params :Sequence [Parameter ],
args :Sequence [Any ],
kwargs :dict [str ,Any ],
)->dict [str ,Any ]:
    """
    Resolve provided arguments according to parameter specifications

    Args:
        params: List of Parameter objects describing the function signature
        args: Tuple of positional arguments
        kwargs: Dictionary of keyword arguments

    Returns:
        Dictionary mapping parameter names to their bound values

    Raises:
        TypeError: If arguments don't match the parameter specification
    """

    result :dict [str ,Any ]={}
    args_iter =iter (args )
    used_args_count =0 


    has_var_positional =any (p .kind ==inspect .Parameter .VAR_POSITIONAL for p in params )


    for param in params :
        if param .kind ==inspect .Parameter .VAR_POSITIONAL :
            remaining =tuple (args_iter )
            result [param .name ]=remaining 
            used_args_count +=len (remaining )
            break 
        else :
            try :
                value =next (args_iter )
                result [param .name ]=value 
                used_args_count +=1 
            except StopIteration :


                if param .name in kwargs :
                    result [param .name ]=kwargs .pop (param .name )
                elif param .default is not inspect ._empty :
                    result [param .name ]=param .default 
                else :
                    raise TypeError (f"Missing required argument: {param .name }")


    if not has_var_positional :
        leftover_positional =list (args_iter )
        if leftover_positional :
            raise TypeError (
            f"Too many positional arguments: expected at most {used_args_count }, "
            f"got {used_args_count +len (leftover_positional )}"
            )


    for param in params :
        if param .kind ==inspect .Parameter .KEYWORD_ONLY :
            if param .name in kwargs :
                result [param .name ]=kwargs .pop (param .name )
            elif param .default is not inspect ._empty :
                result [param .name ]=param .default 
            else :
                raise TypeError (f"Missing required keyword-only argument: {param .name }")


    found_varkw =False 
    for param in params :
        if param .kind ==inspect .Parameter .VAR_KEYWORD :
            result [param .name ]=dict (kwargs )
            kwargs .clear ()
            found_varkw =True 
            break 

    if not found_varkw and kwargs :
        raise TypeError (f"Unexpected keyword arguments: {', '.join (kwargs .keys ())}")

    return result 


def bind_arguments (
func_def :ast .FunctionDef ,
args :Sequence [Any ],
kwargs :dict [str ,Any ],
)->dict [str ,Any ]:
    """
    Bind call arguments to a function definition

    Args:
        func_def: AST node of function definition
        args: Tuple of positional arguments
        kwargs: Dictionary of keyword arguments

    Returns:
        Dictionary mapping parameter names to their bound values
    """
    params =get_signature_from_ast (func_def )
    return resolve_call_arguments (params ,args ,kwargs )
