from typing import Callable, Mapping, Optional
import pydantic_core
from instaui.internal.codec.adapter import empty_type_adapter
from instaui.protocol.codec.type_adapter import TypeAdapterProtocol
from instaui.systems.dataclass_system import dataclass, field
from instaui.systems import func_system


@dataclass()
class HandlerInfo:
    fn: Callable
    fn_location_info: str
    outputs_binding_count: int = 0
    handler_param_converters: list[TypeAdapterProtocol] = field(default_factory=list)
    is_last_param_args: bool = False

    def get_handler_args(self, input_values: list):
        real_param_converters = _try_expand_params_converters(
            self.handler_param_converters, input_values, self.is_last_param_args
        )

        try:
            return [
                param_converter(value)
                for param_converter, value in zip(real_param_converters, input_values)
            ]
        except pydantic_core._pydantic_core.ValidationError as e:
            raise ValueError(f"invalid input[{self.fn_location_info}]: {e}") from None

    @classmethod
    def from_handler(
        cls,
        handler: Callable,
        outputs_binding_count: int,
        custom_type_adapter_map: Optional[Mapping[int, TypeAdapterProtocol]] = None,
        *,
        skip_convert_param=False,
    ):
        custom_type_adapter_map = custom_type_adapter_map or {}
        params_infos = func_system.get_fn_params_infos(handler)
        is_last_param_args = func_system.is_last_param_args(handler)
        param_converters = (
            []
            if skip_convert_param
            else [
                custom_type_adapter_map.get(idx, empty_type_adapter)
                for idx, (_, param_type) in enumerate(params_infos)
            ]
        )

        file, lineno, _ = func_system.get_function_location_info(handler)

        return cls(
            handler,
            f'File "{file}", line {lineno}',
            outputs_binding_count,
            handler_param_converters=param_converters,
            is_last_param_args=is_last_param_args,
        )


def _try_expand_params_converters(
    old_param_converters: list[TypeAdapterProtocol],
    input_values: list,
    is_last_param_args: bool,
):
    if not is_last_param_args:
        return old_param_converters

    diff = len(input_values) - len(old_param_converters)
    if diff == 0:
        return old_param_converters

    arg_param_converters = [old_param_converters[-1]] * diff

    return [*old_param_converters[:], *arg_param_converters]
