import inspect
import re
import typing
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass
from datetime import date, datetime, time
from decimal import Decimal
from enum import Enum
from io import StringIO
from types import FunctionType, NoneType, UnionType
from typing import (
    IO,
    Annotated,
    Any,
    ClassVar,
    Generator,
    Generic,
    Literal,
    Type,
    TypeVar,
    get_origin,
)
from uuid import UUID

from fastapi import Request, Response, UploadFile
from fastapi.params import Body, Cookie, Depends, Header, Path, Query
from fastapi.security.http import HTTPBase
from pydantic import BaseModel, PlainValidator
from pydantic_core import PydanticUndefined

from jararaca.microservice import Microservice
from jararaca.presentation.decorators import HttpMapping, RestController
from jararaca.presentation.websocket.decorators import RegisterWebSocketMessage
from jararaca.presentation.websocket.websocket_interceptor import (
    WebSocketMessageWrapper,
)
from jararaca.tools.typescript.decorators import (
    MutationEndpoint,
    QueryEndpoint,
    SplitInputOutput,
)

CONSTANT_PATTERN = re.compile(r"^[A-Z_]+$")


def is_constant(name: str) -> bool:
    return CONSTANT_PATTERN.match(name) is not None


def should_exclude_field(
    field_name: str, field_type: Any, basemodel_type: Type[Any]
) -> bool:
    """
    Check if a field should be excluded from TypeScript interface generation.

    Args:
        field_name: The name of the field
        field_type: The type annotation of the field
        basemodel_type: The BaseModel class containing the field

    Returns:
        True if the field should be excluded, False otherwise
    """
    # Check if field is private (starts with underscore)
    if field_name.startswith("_"):
        return True

    # Check if field has Pydantic Field annotation and is excluded via model_fields
    if (
        hasattr(basemodel_type, "model_fields")
        and field_name in basemodel_type.model_fields
    ):
        field_info = basemodel_type.model_fields[field_name]

        # Check if field is excluded via Field(exclude=True)
        if hasattr(field_info, "exclude") and field_info.exclude:
            return True

        # Check if field is marked as private via Field(..., alias=None) pattern
        if (
            hasattr(field_info, "alias")
            and field_info.alias is None
            and field_name.startswith("_")
        ):
            return True

    # Check for Annotated types with Field metadata
    if get_origin(field_type) == Annotated:
        for metadata in field_type.__metadata__:
            # Check if this is a Pydantic Field by looking for expected attributes
            if hasattr(metadata, "exclude") or hasattr(metadata, "alias"):
                # Check if Field has exclude=True
                if hasattr(metadata, "exclude") and metadata.exclude:
                    return True
                # Check for private fields with alias=None
                if (
                    hasattr(metadata, "alias")
                    and metadata.alias is None
                    and field_name.startswith("_")
                ):
                    return True

    # Check for Field instances assigned as default values
    # This handles cases like: field_name: str = Field(exclude=True)
    if (
        hasattr(basemodel_type, "__annotations__")
        and field_name in basemodel_type.__annotations__
    ):
        # Check if there's a default value that's a Field instance
        if hasattr(basemodel_type, field_name):
            default_value = getattr(basemodel_type, field_name, None)
            # Check if default value has Field-like attributes (duck typing approach)
            if default_value is not None and hasattr(default_value, "exclude"):
                if getattr(default_value, "exclude", False):
                    return True
            # Check for private fields with alias=None in default Field
            if (
                default_value is not None
                and hasattr(default_value, "alias")
                and getattr(default_value, "alias", None) is None
                and field_name.startswith("_")
            ):
                return True

    return False


def has_default_value(
    field_name: str, field_type: Any, basemodel_type: Type[Any]
) -> bool:
    """
    Check if a field has a default value (making it optional in TypeScript).

    Args:
        field_name: The name of the field
        field_type: The type annotation of the field
        basemodel_type: The BaseModel class containing the field

    Returns:
        True if the field has a default value, False otherwise
    """
    # Skip literal types as they don't have defaults in the traditional sense
    if get_origin(field_type) is Literal:
        return False

    # Check if field has default in model_fields (standard Pydantic way)
    if (
        hasattr(basemodel_type, "model_fields")
        and field_name in basemodel_type.model_fields
    ):
        field_info = basemodel_type.model_fields[field_name]
        if field_info.default is not PydanticUndefined:
            return True

    # Check for Field instances assigned as default values
    # This handles cases like: field_name: str = Field(default="value")
    if (
        hasattr(basemodel_type, "__annotations__")
        and field_name in basemodel_type.__annotations__
    ):
        if hasattr(basemodel_type, field_name):
            default_value = getattr(basemodel_type, field_name, None)
            # Check if it's a Field instance with a default
            if default_value is not None and hasattr(default_value, "default"):
                # Check if the Field has a default value set
                field_default = getattr(default_value, "default", PydanticUndefined)
                if field_default is not PydanticUndefined:
                    return True

    # Check for non-Field default values assigned directly to class attributes
    # This handles cases like: field_name: str = "default_value"
    if hasattr(basemodel_type, field_name):
        default_value = getattr(basemodel_type, field_name, None)
        # If it's not a Field instance but has a value, it's a default
        if (
            default_value is not None
            and not hasattr(default_value, "exclude")  # Not a Field instance
            and not hasattr(default_value, "alias")
        ):  # Not a Field instance
            return True

    # Check for Annotated types with Field metadata that have defaults
    if get_origin(field_type) == Annotated:
        for metadata in field_type.__metadata__:
            # Check if this is a Pydantic Field with a default
            if hasattr(metadata, "default") and hasattr(
                metadata, "exclude"
            ):  # Ensure it's a Field
                field_default = getattr(metadata, "default", PydanticUndefined)
                if field_default is not PydanticUndefined:
                    return True

    return False


class ParseContext:
    def __init__(self) -> None:
        self.mapped_types: set[Any] = set()


parse_context_ctxvar = ContextVar("parse_context", default=ParseContext())


@contextmanager
def with_parse_context() -> Generator[ParseContext, None, None]:
    parse_context = parse_context_ctxvar.get()
    yield parse_context
    parse_context_ctxvar.set(parse_context)


def use_parse_context() -> ParseContext:
    return parse_context_ctxvar.get()


def snake_to_camel(snake_str: str) -> str:
    components = snake_str.split("_")
    return components[0] + "".join(x.title() for x in components[1:])


def pascal_to_camel(pascal_str: str) -> str:
    """Convert a PascalCase string to camelCase."""
    if not pascal_str:
        return pascal_str
    return pascal_str[0].lower() + pascal_str[1:]


def parse_literal_value(value: Any) -> str:
    if value is None:
        return "null"
    if isinstance(value, Enum):
        use_parse_context().mapped_types.add(value.__class__)
        return f"{value.__class__.__name__}.{value.name}"
    if isinstance(value, str):
        # Properly escape quotes for TypeScript string literals
        escaped_value = value.replace("\\", "\\\\").replace('"', '\\"')
        return f'"{escaped_value}"'
    if isinstance(value, float):
        return str(value)
    if isinstance(value, bool):
        # Ensure Python's True/False are properly converted to JavaScript's true/false
        return "true" if value else "false"
    # Special handling for Python symbols that might appear in literal types
    if value is True:
        return "true"
    if value is False:
        return "false"
    if value is None:
        return "null"
    if isinstance(value, int):
        return str(value)
    return "unknown"


def get_field_type_for_ts(field_type: Any, context_suffix: str = "") -> Any:
    """
    Convert a Python type to its TypeScript equivalent.

    Args:
        field_type: The Python type to convert
        context_suffix: Suffix for split models (e.g., "Input", "Output")
    """
    if field_type is Response:
        return "unknown"
    if field_type is Any:
        return "any"
    if field_type == UploadFile:
        return "File"
    if field_type == time:
        return "string"
    if field_type == date:
        return "string"
    if field_type == datetime:
        return "string"
    if field_type == NoneType:
        return "null"
    if field_type == UUID:
        return "string"
    if field_type == str:
        return "string"
    if field_type == int:
        return "number"
    if field_type == float:
        return "number"
    if field_type == bool:
        return "boolean"
    if field_type == Decimal:
        return "number"
    if get_origin(field_type) == ClassVar:
        return get_field_type_for_ts(field_type.__args__[0], context_suffix)
    if get_origin(field_type) == tuple:
        return f"[{', '.join([get_field_type_for_ts(field, context_suffix) for field in field_type.__args__])}]"
    if get_origin(field_type) == list or get_origin(field_type) == frozenset:
        return f"Array<{get_field_type_for_ts(field_type.__args__[0], context_suffix)}>"
    if get_origin(field_type) == set:
        return f"Array<{get_field_type_for_ts(field_type.__args__[0], context_suffix)}> // Set"
    if get_origin(field_type) == dict:
        return f"{{[key: {get_field_type_for_ts(field_type.__args__[0], context_suffix)}]: {get_field_type_for_ts(field_type.__args__[1], context_suffix)}}}"
    if inspect.isclass(field_type):
        if not hasattr(field_type, "__pydantic_generic_metadata__"):
            # Check if this is a split model and use appropriate suffix
            if SplitInputOutput.is_split_model(field_type) and context_suffix:
                return f"{field_type.__name__}{context_suffix}"
            return field_type.__name__
        pydantic_metadata = getattr(field_type, "__pydantic_generic_metadata__")

        name = (
            pydantic_metadata.get("origin").__name__
            if pydantic_metadata.get("origin") is not None
            else field_type.__name__
        )

        # Check if this is a split model and use appropriate suffix
        if SplitInputOutput.is_split_model(field_type) and context_suffix:
            name = f"{field_type.__name__}{context_suffix}"

        args = pydantic_metadata.get("args")

        if len(args) > 0:
            return "%s<%s>" % (
                name,
                ", ".join([get_field_type_for_ts(arg, context_suffix) for arg in args]),
            )

        return name

    if hasattr(field_type, "__class__") and field_type.__class__ is TypeVar:
        return field_type.__name__
    if get_origin(field_type) == Literal:
        return " | ".join([parse_literal_value(x) for x in field_type.__args__])
    if get_origin(field_type) == UnionType or get_origin(field_type) == typing.Union:
        return " | ".join(
            [get_field_type_for_ts(x, context_suffix) for x in field_type.__args__]
        )
    if (get_origin(field_type) == Annotated) and (len(field_type.__args__) > 0):
        if (
            plain_validator := next(
                (x for x in field_type.__metadata__ if isinstance(x, PlainValidator)),
                None,
            )
        ) is not None:
            return get_field_type_for_ts(
                plain_validator.json_schema_input_type, context_suffix
            )
        return get_field_type_for_ts(field_type.__args__[0], context_suffix)
    return "unknown"


def is_generic_type(field_type: Any) -> bool:
    return hasattr(field_type, "__parameters__") and len(field_type.__parameters__) > 0


def get_generic_args(field_type: Any) -> Any:
    return field_type.__parameters__


def parse_type_to_typescript_interface(
    basemodel_type: Type[Any],
) -> tuple[set[type], str]:
    """
    Parse a Pydantic model into TypeScript interface(s).

    If the model is decorated with @SplitInputOutput, it generates both Input and Output interfaces.
    Otherwise, it generates a single interface.
    """
    # Check if this model should be split into Input/Output interfaces
    if SplitInputOutput.is_split_model(basemodel_type):
        return parse_split_input_output_interfaces(basemodel_type)

    return parse_single_typescript_interface(basemodel_type)


def parse_split_input_output_interfaces(
    basemodel_type: Type[Any],
) -> tuple[set[type], str]:
    """
    Generate both Input and Output TypeScript interfaces for a split model.
    """
    mapped_types: set[type] = set()
    combined_output = StringIO()

    # Generate Input interface (with optional fields)
    input_mapped_types, input_interface = parse_single_typescript_interface(
        basemodel_type, interface_suffix="Input", force_optional_defaults=True
    )
    mapped_types.update(input_mapped_types)
    combined_output.write(input_interface)

    # Generate Output interface (all fields required as they come from the backend)
    output_mapped_types, output_interface = parse_single_typescript_interface(
        basemodel_type, interface_suffix="Output", force_optional_defaults=False
    )
    mapped_types.update(output_mapped_types)
    combined_output.write(output_interface)

    return mapped_types, combined_output.getvalue()


def parse_single_typescript_interface(
    basemodel_type: Type[Any],
    interface_suffix: str = "",
    force_optional_defaults: bool | None = None,
) -> tuple[set[type], str]:
    """
    Generate a single TypeScript interface for a Pydantic model.

    Args:
        basemodel_type: The Pydantic model class
        interface_suffix: Suffix to add to the interface name (e.g., "Input", "Output")
        force_optional_defaults: If True, fields with defaults are optional. If False, all fields are required.
                                If None, uses the default behavior (fields with defaults are optional).
    """
    string_builder = StringIO()
    mapped_types: set[type] = set()

    inherited_classes = get_inherited_classes(basemodel_type)

    if (orig_type := get_origin(basemodel_type)) is not None:
        mapped_types.update(basemodel_type.__args__)
        mapped_types.add(orig_type)
        return mapped_types, ""

    if hasattr(basemodel_type, "__pydantic_generic_metadata__"):
        metadata = getattr(basemodel_type, "__pydantic_generic_metadata__")
        if metadata.get("origin") is not None:
            mapped_types.add(metadata.get("origin"))

            for arg in metadata.get("args"):
                mapped_types.update(extract_all_envolved_types(arg))

            return mapped_types, ""

    mapped_types.update(inherited_classes)

    if Enum in inherited_classes:
        enum_values = sorted([(x._name_, x.value) for x in basemodel_type])
        return (
            set(),
            f"export enum {basemodel_type.__name__}{interface_suffix} {{\n"
            + "\n ".join([f'\t{name} = "{value}",' for name, value in enum_values])
            + "\n}\n",
        )

    valid_inherited_classes = [
        inherited_class
        for inherited_class in inherited_classes
        if inherited_class is not BaseModel
        if not is_primitive(inherited_class)
    ]

    cls_consts = set(
        field_name for field_name in (f for f in dir(basemodel_type) if is_constant(f))
    )

    inherited_classes_consts_conflict = {
        inherited_class: set(
            field_name
            for field_name in (f for f in dir(inherited_class) if is_constant(f))
        ).intersection(cls_consts)
        for inherited_class in valid_inherited_classes
    }

    # Modify inheritance for split interfaces
    extends_expression = ""
    if len(valid_inherited_classes) > 0:
        extends_base_names = []
        for inherited_class in valid_inherited_classes:
            base_name = get_field_type_for_ts(inherited_class, interface_suffix)
            # If the inherited class is also a split model, use the appropriate suffix
            if SplitInputOutput.is_split_model(inherited_class) and interface_suffix:
                base_name = f"{inherited_class.__name__}{interface_suffix}"

            if inherited_classes_consts_conflict[inherited_class]:
                base_name = "Omit<%s, %s>" % (
                    base_name,
                    " | ".join(
                        sorted(
                            [
                                '"%s"' % field_name
                                for field_name in inherited_classes_consts_conflict[
                                    inherited_class
                                ]
                            ]
                        )
                    ),
                )
            extends_base_names.append(base_name)

        extends_expression = " extends %s" % ", ".join(
            sorted(extends_base_names, key=lambda x: str(x))
        )

    interface_name = f"{basemodel_type.__name__}{interface_suffix}"

    if is_generic_type(basemodel_type):
        generic_args = get_generic_args(basemodel_type)
        string_builder.write(
            f"export interface {interface_name}<{', '.join(sorted([arg.__name__ for arg in generic_args]))}>{extends_expression} {{\n"
        )
    else:
        string_builder.write(
            f"export interface {interface_name}{extends_expression} {{\n"
        )

    if hasattr(basemodel_type, "__annotations__"):
        # Sort fields for consistent output
        annotation_items = sorted(
            basemodel_type.__annotations__.items(), key=lambda x: x[0]
        )

        for field_name, field in annotation_items:
            if field_name in cls_consts:
                continue

            # Check if field should be excluded (private or excluded via Field)
            if should_exclude_field(field_name, field, basemodel_type):
                continue

            # Determine if field is optional based on the force_optional_defaults parameter
            if force_optional_defaults is True:
                # Input interface: fields with defaults are optional
                is_optional = has_default_value(field_name, field, basemodel_type)
            elif force_optional_defaults is False:
                # Output interface: all fields are required (backend provides complete data)
                is_optional = False
            else:
                # Default behavior: fields with defaults are optional
                is_optional = has_default_value(field_name, field, basemodel_type)

            string_builder.write(
                f"  {snake_to_camel(field_name) if not is_constant(field_name) else field_name}{'?' if is_optional else ''}: {get_field_type_for_ts(field, interface_suffix)};\n"
            )
            mapped_types.update(extract_all_envolved_types(field))
            mapped_types.add(field)

    ## Loop over computed fields - sort them for consistent output
    members = sorted(
        inspect.getmembers(basemodel_type, lambda a: isinstance(a, property)),
        key=lambda x: x[0],
    )
    for field_name, field in members:
        if hasattr(field, "fget"):
            module_func_name = field.fget.__module__ + "." + field.fget.__qualname__
            if (
                module_func_name
                == basemodel_type.__module__
                + "."
                + basemodel_type.__name__
                + "."
                + field_name
            ):

                return_type = field.fget.__annotations__.get("return")
                if return_type is None:
                    return_type = NoneType

                string_builder.write(
                    f"  {snake_to_camel(field_name)}: {get_field_type_for_ts(return_type, interface_suffix)};\n"
                )
                mapped_types.update(extract_all_envolved_types(return_type))
                mapped_types.add(return_type)

    string_builder.write("}\n")

    return mapped_types, string_builder.getvalue()


def get_inherited_classes(basemodel_type: Type[Any]) -> list[Type[Any]]:
    if not hasattr(basemodel_type, "__bases__"):
        return []
    return [
        base_class
        for base_class in basemodel_type.__bases__
        if get_origin(base_class) is not Generic
        if base_class is not BaseModel
    ]


def write_microservice_to_typescript_interface(
    microservice: Microservice,
) -> str:
    rest_controller_buffer = StringIO()
    mapped_types_set: set[Any] = set()

    websocket_registries: set[RegisterWebSocketMessage] = set()
    mapped_types_set.add(WebSocketMessageWrapper)

    for controller in microservice.controllers:
        rest_controller = RestController.get_controller(controller)

        if rest_controller is None:
            continue

        controller_class_strio, types, hooks_strio = (
            write_rest_controller_to_typescript_interface(
                rest_controller,
                controller,
            )
        )

        mapped_types_set.update(types)
        rest_controller_buffer.write(controller_class_strio.getvalue())
        if hooks_strio is not None:
            rest_controller_buffer.write(hooks_strio.getvalue())

        registered = RegisterWebSocketMessage.get(controller)

        if registered is not None:
            for message_type in registered.message_types:
                identified_types, text = parse_type_to_typescript_interface(
                    message_type
                )
                websocket_registries.add(registered)
                mapped_types_set.update(identified_types)
                rest_controller_buffer.write(text)

    final_buffer = StringIO()

    final_buffer.write(
        """
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

import { HttpService, HttpBackend, HttpBackendRequest, ResponseType, createClassQueryHooks , createClassMutationHooks, createClassInfiniteQueryHooks, paginationModelByFirstArgPaginationFilter } from "@jararaca/core";
export type WebSocketMessageMap = {
%s
}
"""
        % "\n".join(
            sorted(
                [
                    f'\t"{message.MESSAGE_ID}": {message.__name__};'
                    for registers in websocket_registries
                    for message in registers.message_types
                ]
            )
        )
    )

    processed_types: set[Any] = set()
    backlog: set[Any] = mapped_types_set.copy()

    unordered_types_buffer: list[tuple[type, str]] = []

    while len(backlog) > 0:
        t = backlog.pop()
        if not is_primitive(t):
            with with_parse_context() as parse_context:

                identified_types, text = parse_type_to_typescript_interface(t)
                processed_types.add(t)
                # final_buffer.write(text)
                unordered_types_buffer.append((t, text))
                identified_types.update(parse_context.mapped_types)
                backlog.update(identified_types - processed_types)

    ordered_types_buffer = sorted(unordered_types_buffer, key=lambda x: x[0].__name__)

    for _, text in ordered_types_buffer:
        final_buffer.write(text)

    final_buffer.write(rest_controller_buffer.getvalue())

    return final_buffer.getvalue()


def is_primitive(field_type: Any) -> bool:
    return (
        field_type
        in [
            str,
            int,
            float,
            bool,
            NoneType,
            UUID,
            Decimal,
            date,
            datetime,
            object,
            Enum,
            set,
            UploadFile,
            IO,
            time,
            Any,
        ]
        or get_origin(field_type)
        in [list, dict, tuple, Literal, UnionType, Annotated, set]
        or isinstance(
            field_type,
            (
                str,
                int,
                float,
                bool,
                NoneType,
                UUID,
                Decimal,
                date,
                datetime,
                Enum,
                TypeVar,
                set,
            ),
        )
    )


def write_rest_controller_to_typescript_interface(
    rest_controller: RestController, controller: type
) -> tuple[StringIO, set[Any], StringIO | None]:

    class_name = controller.__name__

    decorated_queries: list[tuple[str, FunctionType, QueryEndpoint]] = []
    decorated_mutations: list[tuple[str, FunctionType]] = []

    class_buffer = StringIO()

    class_buffer.write(f"export class {class_name} extends HttpService {{\n")

    mapped_types: set[Any] = set()

    # Sort members for consistent output
    member_items = sorted(
        inspect.getmembers(controller, predicate=inspect.isfunction), key=lambda x: x[0]
    )

    for name, member in member_items:
        if (mapping := HttpMapping.get_http_mapping(member)) is not None:
            return_type = member.__annotations__.get("return")

            if return_type is None:
                return_type = NoneType

            if query_endpoint := QueryEndpoint.extract_query_endpoint(member):
                decorated_queries.append((name, member, query_endpoint))
            if MutationEndpoint.is_mutation(member):
                decorated_mutations.append((name, member))

            mapped_types.update(extract_all_envolved_types(return_type))

            # For return types, use Output suffix if it's a split model
            return_value_repr = get_field_type_for_ts(return_type, "Output")

            arg_params_spec, parametes_mapped_types = extract_parameters(
                member, rest_controller, mapping
            )

            for param in parametes_mapped_types:
                mapped_types.update(extract_all_envolved_types(param))

            class_buffer.write(
                f"\tasync {snake_to_camel(name)}({mount_parametes_arguments(arg_params_spec)}): Promise<{return_value_repr}> {{\n"
            )

            class_buffer.write("\t\t")
            class_buffer.write(
                "const response = await this.httpBackend.request<%s>({ \n"
                % return_value_repr
            )
            if mapping.response_type is not None:
                class_buffer.write(f'\t\t\tresponseType: "{mapping.response_type}",\n')
            class_buffer.write(f'\t\t\tmethod: "{mapping.method}",\n')

            endpoint_path = parse_path_with_params(mapping.path, arg_params_spec)
            final_path = "/".join(
                s.strip("/") for s in [rest_controller.path, endpoint_path]
            )
            class_buffer.write(f"\t\t\tpath: `/{final_path}`,\n")

            # Sort path params
            path_params = sorted(
                [param for param in arg_params_spec if param.type_ == "path"],
                key=lambda x: x.name,
            )
            class_buffer.write("\t\t\tpathParams: {\n")
            for param in path_params:
                class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
            class_buffer.write("\t\t\t},\n")

            # Sort headers
            header_params = sorted(
                [param for param in arg_params_spec if param.type_ == "header"],
                key=lambda x: x.name,
            )
            class_buffer.write("\t\t\theaders: {\n")
            for param in header_params:
                class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
            class_buffer.write("\t\t\t},\n")

            # Sort query params
            query_params = sorted(
                [param for param in arg_params_spec if param.type_ == "query"],
                key=lambda x: x.name,
            )
            class_buffer.write("\t\t\tquery: {\n")
            for param in query_params:
                class_buffer.write(f'\t\t\t\t"{param.name}": {param.name},\n')
            class_buffer.write("\t\t\t},\n")

            if (
                body := next((x for x in arg_params_spec if x.type_ == "body"), None)
            ) is not None:
                class_buffer.write(f"\t\t\tbody: {body.name}\n")
            else:
                class_buffer.write("\t\t\tbody: undefined\n")

            class_buffer.write("\t\t});\n")
            class_buffer.write("\t\treturn response;\n")

            class_buffer.write("\t}\n")

    class_buffer.write("}\n")

    controller_hooks_builder: StringIO | None = None

    if decorated_queries or decorated_mutations:
        controller_hooks_builder = StringIO()
        controller_hooks_builder.write(
            f"export const {pascal_to_camel(class_name)} = {{\n"
        )

        if decorated_queries:
            controller_hooks_builder.write(
                f"\t...createClassQueryHooks({class_name},\n"
            )
            for name, member, _ in decorated_queries:
                controller_hooks_builder.write(f'\t\t"{snake_to_camel(name)}",\n')
            controller_hooks_builder.write("\t),\n")

        if decorated_queries and any(
            query.has_infinite_query for _, _, query in decorated_queries
        ):
            controller_hooks_builder.write(
                f"\t...createClassInfiniteQueryHooks({class_name}, {{\n"
            )
            for name, member, query in decorated_queries:
                if query.has_infinite_query:
                    controller_hooks_builder.write(
                        f'\t\t"{snake_to_camel(name)}": paginationModelByFirstArgPaginationFilter(),\n'
                    )
            controller_hooks_builder.write("\t}),\n")
        if decorated_mutations:
            controller_hooks_builder.write(
                f"\t...createClassMutationHooks({class_name},\n"
            )
            for name, member in decorated_mutations:
                controller_hooks_builder.write(f'\t\t"{snake_to_camel(name)}",\n')
            controller_hooks_builder.write("\t),\n")
        controller_hooks_builder.write("};\n")

    return class_buffer, mapped_types, controller_hooks_builder


EXCLUDED_REQUESTS_TYPES = [Request, Response]


@dataclass
class HttpParemeterSpec:
    type_: Literal["query", "path", "body", "header", "cookie"]
    name: str
    required: bool
    argument_type_str: str


def parse_path_with_params(path: str, parameters: list[HttpParemeterSpec]) -> str:
    # Use a regular expression to match both simple parameters {param} and
    # parameters with converters {param:converter}
    import re

    pattern = re.compile(r"{([^:}]+)(?::[^}]*)?}")

    # For each parameter found in the path, replace it with :param format
    for parameter in parameters:
        path = pattern.sub(
            lambda m: f":{m.group(1)}" if m.group(1) == parameter.name else m.group(0),
            path,
        )

    return path


def mount_parametes_arguments(parameters: list[HttpParemeterSpec]) -> str:
    return ", ".join(
        [f"{parameter.name}: {parameter.argument_type_str}" for parameter in parameters]
    )


def extract_parameters(
    member: Any, controller: RestController, mapping: HttpMapping
) -> tuple[list[HttpParemeterSpec], set[Any]]:
    parameters_list: list[HttpParemeterSpec] = []
    mapped_types: set[Any] = set()
    if get_origin(member) is UnionType:
        for arg in member.__args__:
            if is_primitive(arg):
                continue
            rec_parameters, rec_mapped_types = extract_parameters(
                arg, controller, mapping
            )
            mapped_types.update(rec_mapped_types)
            parameters_list.extend(rec_parameters)
        return parameters_list, mapped_types

    if is_primitive(member):

        if get_origin(member) is Annotated:
            if (
                plain_validator := next(
                    (x for x in member.__metadata__ if isinstance(x, PlainValidator)),
                    None,
                )
            ) is not None:
                mapped_types.add(plain_validator.json_schema_input_type)
                return parameters_list, mapped_types
            return extract_parameters(member.__args__[0], controller, mapping)
        return parameters_list, mapped_types

    if hasattr(member, "__bases__"):
        for base in member.__bases__:
            # if base is not BaseModel:
            rec_parameters, rec_mapped_types = extract_parameters(
                base, controller, mapping
            )
            mapped_types.update(rec_mapped_types)
            parameters_list.extend(rec_parameters)

    if hasattr(member, "__annotations__"):
        for parameter_name, parameter_type in member.__annotations__.items():
            if parameter_name == "return":
                continue
            if parameter_type in EXCLUDED_REQUESTS_TYPES:
                continue

            if get_origin(parameter_type) == Annotated:
                annotated_type_hook = parameter_type.__metadata__[0]
                annotated_type = parameter_type.__args__[0]
                if isinstance(annotated_type_hook, Header):
                    mapped_types.add(str)
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="header",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(str),
                        )
                    )
                elif isinstance(annotated_type_hook, Cookie):
                    mapped_types.add(str)
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="cookie",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(str),
                        )
                    )
                elif isinstance(annotated_type_hook, Body):
                    mapped_types.update(extract_all_envolved_types(parameter_type))
                    # For body parameters, use Input suffix if it's a split model
                    context_suffix = (
                        "Input"
                        if (
                            inspect.isclass(parameter_type)
                            and hasattr(parameter_type, "__dict__")
                            and SplitInputOutput.is_split_model(parameter_type)
                        )
                        else ""
                    )
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="body",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(
                                parameter_type, context_suffix
                            ),
                        )
                    )
                elif isinstance(annotated_type_hook, Query):
                    mapped_types.add(parameter_type)
                    # For query parameters, use Input suffix if it's a split model
                    context_suffix = (
                        "Input"
                        if (
                            inspect.isclass(parameter_type)
                            and hasattr(parameter_type, "__dict__")
                            and SplitInputOutput.is_split_model(parameter_type)
                        )
                        else ""
                    )
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="query",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(
                                parameter_type, context_suffix
                            ),
                        )
                    )
                elif isinstance(annotated_type_hook, Path):
                    mapped_types.add(parameter_type)
                    # For path parameters, use Input suffix if it's a split model
                    context_suffix = (
                        "Input"
                        if (
                            inspect.isclass(parameter_type)
                            and hasattr(parameter_type, "__dict__")
                            and SplitInputOutput.is_split_model(parameter_type)
                        )
                        else ""
                    )
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="path",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(
                                parameter_type, context_suffix
                            ),
                        )
                    )

                elif isinstance(annotated_type_hook, Depends):
                    depends_hook = (
                        annotated_type_hook.dependency or parameter_type.__args__[0]
                    )

                    if isinstance(depends_hook, HTTPBase):
                        ...

                    else:
                        rec_parameters, rec_mapped_types = extract_parameters(
                            depends_hook, controller, mapping
                        )
                        mapped_types.update(rec_mapped_types)
                        parameters_list.extend(rec_parameters)
                elif (
                    re.search(f":{parameter_name}(?:/|$)", controller.path) is not None
                ):
                    mapped_types.add(annotated_type)
                    # For path parameters, use Input suffix if it's a split model
                    context_suffix = (
                        "Input"
                        if (
                            inspect.isclass(annotated_type)
                            and hasattr(annotated_type, "__dict__")
                            and SplitInputOutput.is_split_model(annotated_type)
                        )
                        else ""
                    )
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="path",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(
                                annotated_type, context_suffix
                            ),
                        )
                    )
                else:
                    mapped_types.add(annotated_type)
                    # For default parameters (treated as query), use Input suffix if it's a split model
                    context_suffix = (
                        "Input"
                        if (
                            inspect.isclass(annotated_type)
                            and hasattr(annotated_type, "__dict__")
                            and SplitInputOutput.is_split_model(annotated_type)
                        )
                        else ""
                    )
                    parameters_list.append(
                        HttpParemeterSpec(
                            type_="query",
                            name=parameter_name,
                            required=True,
                            argument_type_str=get_field_type_for_ts(
                                annotated_type, context_suffix
                            ),
                        )
                    )

            elif inspect.isclass(parameter_type) and issubclass(
                parameter_type, BaseModel
            ):
                mapped_types.update(extract_all_envolved_types(parameter_type))
                # For BaseModel parameters, use Input suffix if it's a split model
                context_suffix = (
                    "Input" if SplitInputOutput.is_split_model(parameter_type) else ""
                )
                parameters_list.append(
                    HttpParemeterSpec(
                        type_="body",
                        name=parameter_name,
                        required=True,
                        argument_type_str=get_field_type_for_ts(
                            parameter_type, context_suffix
                        ),
                    )
                )
            elif (
                # Match both simple parameters {param} and parameters with converters {param:converter}
                re.search(f"{{{parameter_name}(:.*?)?}}", controller.path) is not None
                or re.search(f"{{{parameter_name}(:.*?)?}}", mapping.path) is not None
            ):
                mapped_types.add(parameter_type)
                # For path parameters, use Input suffix if it's a split model
                context_suffix = (
                    "Input"
                    if (
                        inspect.isclass(parameter_type)
                        and hasattr(parameter_type, "__dict__")
                        and SplitInputOutput.is_split_model(parameter_type)
                    )
                    else ""
                )
                parameters_list.append(
                    HttpParemeterSpec(
                        type_="path",
                        name=parameter_name,
                        required=True,
                        argument_type_str=get_field_type_for_ts(
                            parameter_type, context_suffix
                        ),
                    )
                )
            else:
                mapped_types.add(parameter_type)
                # For default parameters (treated as query), use Input suffix if it's a split model
                context_suffix = (
                    "Input"
                    if (
                        inspect.isclass(parameter_type)
                        and hasattr(parameter_type, "__dict__")
                        and SplitInputOutput.is_split_model(parameter_type)
                    )
                    else ""
                )
                parameters_list.append(
                    HttpParemeterSpec(
                        type_="query",
                        name=parameter_name,
                        required=True,
                        argument_type_str=get_field_type_for_ts(
                            parameter_type, context_suffix
                        ),
                    )
                )

            if inspect.isclass(parameter_type) and not is_primitive(parameter_type):
                signature = inspect.signature(parameter_type)

                parameter_members = signature.parameters

                for _, parameter_type in parameter_members.items():
                    if is_primitive(parameter_type.annotation):
                        if get_origin(parameter_type.annotation) is not None:
                            if (
                                get_origin(parameter_type.annotation) == Annotated
                                and (
                                    plain_validator := next(
                                        (
                                            x
                                            for x in parameter_type.annotation.__metadata__
                                            if isinstance(x, PlainValidator)
                                        ),
                                        None,
                                    )
                                )
                                is not None
                            ):
                                mapped_types.add(plain_validator.json_schema_input_type)
                            else:
                                args = parameter_type.annotation.__args__
                                mapped_types.update(args)
                        else:
                            continue
                    _, types = extract_parameters(
                        parameter_type.annotation, controller, mapping
                    )
                    mapped_types.update(types)

    if hasattr(member, "__args__"):
        for arg in member.__args__:

            rec_parameters, rec_mapped_types = extract_parameters(
                arg, controller, mapping
            )
            mapped_types.update(rec_mapped_types)
            parameters_list.extend(rec_parameters)

    return parameters_list, mapped_types


def extract_all_envolved_types(field_type: Any) -> set[Any]:
    mapped_types: set[Any] = set()

    if field_type is None:
        return mapped_types

    if is_primitive(field_type):
        if get_origin(field_type) is not None:
            if (
                get_origin(field_type) == Annotated
                and (
                    plain_validator := next(
                        (
                            x
                            for x in field_type.__metadata__
                            if isinstance(x, PlainValidator)
                        ),
                        None,
                    )
                )
                is not None
            ):
                mapped_types.add(plain_validator.json_schema_input_type)
                return mapped_types
            else:
                mapped_types.update(
                    *[extract_all_envolved_types(arg) for arg in field_type.__args__]
                )
                return mapped_types

    if inspect.isclass(field_type):
        if hasattr(field_type, "__pydantic_generic_metadata__"):
            metadata = getattr(field_type, "__pydantic_generic_metadata__")
            if metadata.get("origin") is not None:
                mapped_types.add(metadata.get("origin"))
            else:
                mapped_types.add(field_type)
            for arg in metadata.get("args"):
                mapped_types.update(extract_all_envolved_types(arg))
        else:
            mapped_types.add(field_type)

        if hasattr(field_type, "__annotations__"):
            for member in field_type.__annotations__.values():
                mapped_types.update(extract_all_envolved_types(member))

    if hasattr(field_type, "__args__"):
        for arg in field_type.__args__:
            mapped_types.update(extract_all_envolved_types(arg))

    return mapped_types
